From ae521140dac659790030df7754bc7073847977f9 Mon Sep 17 00:00:00 2001 From: Eelke van den Bos <1001306+eelkevdbos@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:02:42 +0100 Subject: [PATCH 1/2] feat: add tool annotations --- lib/model_context_protocol/server/tool.rb | 10 ++- .../server/tool_spec.rb | 85 +++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/lib/model_context_protocol/server/tool.rb b/lib/model_context_protocol/server/tool.rb index 198755b..383e6b2 100644 --- a/lib/model_context_protocol/server/tool.rb +++ b/lib/model_context_protocol/server/tool.rb @@ -83,7 +83,7 @@ def serialized end class << self - attr_reader :name, :description, :title, :input_schema, :output_schema + attr_reader :name, :description, :title, :input_schema, :output_schema, :annotations def define(&block) definition_dsl = DefinitionDSL.new @@ -94,6 +94,7 @@ def define(&block) @title = definition_dsl.title @input_schema = definition_dsl.input_schema @output_schema = definition_dsl.output_schema + @annotations = definition_dsl.annotations end def inherited(subclass) @@ -102,6 +103,7 @@ def inherited(subclass) subclass.instance_variable_set(:@title, @title) subclass.instance_variable_set(:@input_schema, @input_schema) subclass.instance_variable_set(:@output_schema, @output_schema) + subclass.instance_variable_set(:@annotations, @annotations) end def call(arguments, client_logger, server_logger, context = {}) @@ -120,6 +122,7 @@ def definition result = {name: @name, description: @description, inputSchema: @input_schema} result[:title] = @title if @title result[:outputSchema] = @output_schema if @output_schema + result[:annotations] = @annotations if @annotations result end end @@ -149,6 +152,11 @@ def output_schema(&block) @output_schema = instance_eval(&block) if block_given? @output_schema end + + def annotations(&block) + @annotations = instance_eval(&block) if block_given? + @annotations + end end end end diff --git a/spec/lib/model_context_protocol/server/tool_spec.rb b/spec/lib/model_context_protocol/server/tool_spec.rb index dcc2b24..327784b 100644 --- a/spec/lib/model_context_protocol/server/tool_spec.rb +++ b/spec/lib/model_context_protocol/server/tool_spec.rb @@ -295,6 +295,46 @@ ) end end + + it "sets annotations when provided" do + tool_with_annotations = Class.new(ModelContextProtocol::Server::Tool) do + define do + name "fetch" + description "Fetch the full contents of a single resource" + input_schema do + { + type: "object", + properties: { + id: { + type: "string", + description: "Unique identifier of the resource to fetch" + } + }, + required: ["id"] + } + end + annotations do + {readOnlyHint: true} + end + end + end + + expect(tool_with_annotations.annotations).to eq(readOnlyHint: true) + end + + it "inherits annotations in subclasses" do + parent_tool = Class.new(ModelContextProtocol::Server::Tool) do + define do + name "fetch" + description "Fetch the full contents of a single resource" + input_schema { {type: "object", properties: {}, required: []} } + annotations { {"readOnlyHint": true} } + end + end + + child_tool = Class.new(parent_tool) + expect(child_tool.annotations).to eq({"readOnlyHint": true}) + end end describe "definition" do @@ -350,6 +390,46 @@ } ) end + + it "includes annotations when provided" do + tool_with_annotations = Class.new(ModelContextProtocol::Server::Tool) do + define do + name "fetch" + description "Fetch the full contents of a single resource" + input_schema do + { + type: "object", + properties: { + id: { + type: "string", + description: "Unique identifier of the resource to fetch" + } + }, + required: ["id"] + } + end + annotations do + {"readOnlyHint": true} + end + end + end + + expect(tool_with_annotations.definition).to eq( + name: "fetch", + description: "Fetch the full contents of a single resource", + inputSchema: { + type: "object", + properties: { + id: { + type: "string", + description: "Unique identifier of the resource to fetch" + } + }, + required: ["id"] + }, + annotations: {"readOnlyHint": true} + ) + end end describe "optional title field" do @@ -373,6 +453,11 @@ def call metadata = tool_without_title.definition expect(metadata).not_to have_key(:title) end + + it "does not include annotations in definition when not provided" do + metadata = tool_without_title.definition + expect(metadata).not_to have_key(:annotations) + end end describe "server logger integration" do From 03b5b1cad76cae5752aa92a4babf8da6a49cc052 Mon Sep 17 00:00:00 2001 From: Eelke van den Bos <1001306+eelkevdbos@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:46:12 +0100 Subject: [PATCH 2/2] fix: linting errors --- spec/lib/model_context_protocol/server/tool_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/lib/model_context_protocol/server/tool_spec.rb b/spec/lib/model_context_protocol/server/tool_spec.rb index 327784b..f7f8898 100644 --- a/spec/lib/model_context_protocol/server/tool_spec.rb +++ b/spec/lib/model_context_protocol/server/tool_spec.rb @@ -328,12 +328,12 @@ name "fetch" description "Fetch the full contents of a single resource" input_schema { {type: "object", properties: {}, required: []} } - annotations { {"readOnlyHint": true} } + annotations { {readOnlyHint: true} } end end child_tool = Class.new(parent_tool) - expect(child_tool.annotations).to eq({"readOnlyHint": true}) + expect(child_tool.annotations).to eq({readOnlyHint: true}) end end @@ -409,7 +409,7 @@ } end annotations do - {"readOnlyHint": true} + {readOnlyHint: true} end end end @@ -427,7 +427,7 @@ }, required: ["id"] }, - annotations: {"readOnlyHint": true} + annotations: {readOnlyHint: true} ) end end