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..f7f8898 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