Support Sorbet typed tools

This commit is contained in:
Christopher Butcher 2025-05-16 10:09:31 -04:00
commit 84e83841b0
No known key found for this signature in database
GPG key ID: 604E358E88E26254
3 changed files with 92 additions and 1 deletions

View file

@ -1,3 +1,4 @@
# typed: true
# frozen_string_literal: true
require "test_helper"
@ -256,6 +257,43 @@ module ModelContextProtocol
assert_instrumentation_data({ method: "tools/call", tool_name: })
end
test "#handle_json tools/call executes tool and returns result, when the tool is typed with Sorbet" do
class TypedTestTool < Tool
tool_name "test_tool"
description "a test tool for testing"
input_schema({ properties: { message: { type: "string" } }, required: ["message"] })
class << self
extend T::Sig
sig { params(message: String, server_context: T.nilable(T.untyped)).returns(Tool::Response) }
def call(message:, server_context: nil)
Tool::Response.new([{ type: "text", content: "OK" }])
end
end
end
request = JSON.generate({
jsonrpc: "2.0",
method: "tools/call",
params: { name: "test_tool", arguments: { message: "Hello, world!" } },
id: 1,
})
server = Server.new(
name: @server_name,
tools: [TypedTestTool],
prompts: [@prompt],
resources: [@resource],
resource_templates: [@resource_template],
)
raw_response = server.handle_json(request)
response = JSON.parse(raw_response, symbolize_names: true) if raw_response
assert_equal({ content: [{ type: "text", content: "OK" }], isError: false }, response[:result])
end
test "#handle tools/call returns internal error and reports exception if the tool raises an error" do
@server.configuration.exception_reporter.expects(:call).with do |exception, server_context|
assert_not_nil exception

View file

@ -1,3 +1,4 @@
# typed: true
# frozen_string_literal: true
require "test_helper"
@ -203,5 +204,34 @@ module ModelContextProtocol
tool.annotations(title: "Updated")
assert_equal tool.annotations_value.title, "Updated"
end
test "#call with Sorbet typed tools invokes the tool block and returns the response" do
class TypedTestTool < Tool
tool_name "test_tool"
description "a test tool for testing"
input_schema({ properties: { message: { type: "string" } }, required: ["message"] })
annotations(
title: "Test Tool",
read_only_hint: true,
destructive_hint: false,
idempotent_hint: true,
open_world_hint: false,
)
class << self
extend T::Sig
sig { params(message: String, server_context: T.nilable(T.untyped)).returns(Tool::Response) }
def call(message, server_context: nil)
Tool::Response.new([{ type: "text", content: "OK" }])
end
end
end
tool = TypedTestTool
response = tool.call("test")
assert_equal response.content, [{ type: "text", content: "OK" }]
assert_equal response.is_error, false
end
end
end

View file

@ -13,6 +13,8 @@ require "mocha/minitest"
require "active_support"
require "active_support/test_case"
require "sorbet-runtime"
require_relative "instrumentation_test_helper"
Minitest::Reporters.use!(Minitest::Reporters::ProgressReporter.new)