Internal Release 0.4.0
- Added support for handle_json method from JSON RPC gem to allow consumers to pass JSON strings - Added context parameter to prompt templating system - Updated tool and prompt registration to use class references instead of instances - Changed tool response is_error to isError to match protocol specification
This commit is contained in:
parent
0645351585
commit
2eca3681df
4 changed files with 306 additions and 151 deletions
|
|
@ -45,9 +45,19 @@ module ModelContextProtocol
|
|||
end
|
||||
|
||||
class << self
|
||||
NOT_SET = Object.new
|
||||
|
||||
attr_reader :description_value
|
||||
attr_reader :arguments_value
|
||||
|
||||
def template(args, context:)
|
||||
raise NotImplementedError, "Subclasses must implement template"
|
||||
end
|
||||
|
||||
def to_h
|
||||
{ name: name_value, description: description_value, arguments: arguments_value.map(&:to_h) }.compact
|
||||
end
|
||||
|
||||
def inherited(subclass)
|
||||
super
|
||||
subclass.instance_variable_set(:@name_value, nil)
|
||||
|
|
@ -55,58 +65,57 @@ module ModelContextProtocol
|
|||
subclass.instance_variable_set(:@arguments_value, nil)
|
||||
end
|
||||
|
||||
def prompt_name(value)
|
||||
@name_value = value
|
||||
def prompt_name(value = NOT_SET)
|
||||
if value == NOT_SET
|
||||
@name_value
|
||||
else
|
||||
@name_value = value
|
||||
end
|
||||
end
|
||||
|
||||
def name_value
|
||||
@name_value || StringUtils.handle_from_class_name(name)
|
||||
end
|
||||
|
||||
def description(value)
|
||||
@description_value = value
|
||||
def description(value = NOT_SET)
|
||||
if value == NOT_SET
|
||||
@description_value
|
||||
else
|
||||
@description_value = value
|
||||
end
|
||||
end
|
||||
|
||||
def arguments(value)
|
||||
@arguments_value = value
|
||||
def arguments(value = NOT_SET)
|
||||
if value == NOT_SET
|
||||
@arguments_value
|
||||
else
|
||||
@arguments_value = value
|
||||
end
|
||||
end
|
||||
|
||||
def define(name: nil, description: nil, arguments: [], &block)
|
||||
new(name:, description:, arguments:).tap do |prompt|
|
||||
prompt.define_singleton_method(:template) do |args|
|
||||
instance_exec(args, &block)
|
||||
Class.new(self) do
|
||||
prompt_name name
|
||||
description description
|
||||
arguments arguments
|
||||
define_singleton_method(:template) do |args, context:|
|
||||
instance_exec(args, context:, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :name, :description, :arguments
|
||||
def validate_arguments!(args)
|
||||
missing = required_args - args.keys
|
||||
return if missing.empty?
|
||||
|
||||
def initialize(name: nil, description: nil, arguments: nil)
|
||||
@name = name || self.class.name_value
|
||||
@description = description || self.class.description_value
|
||||
@arguments = arguments || self.class.arguments_value
|
||||
end
|
||||
raise ArgumentError, "Missing required arguments: #{missing.join(", ")}"
|
||||
end
|
||||
|
||||
def template(args)
|
||||
raise NotImplementedError, "Prompt subclasses must implement template"
|
||||
end
|
||||
private
|
||||
|
||||
def validate_arguments!(args)
|
||||
missing = required_args - args.keys
|
||||
return if missing.empty?
|
||||
|
||||
raise ArgumentError, "Missing required arguments: #{missing.join(", ")}"
|
||||
end
|
||||
|
||||
def to_h
|
||||
{ name:, description:, arguments: arguments.map(&:to_h) }.compact
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def required_args
|
||||
arguments.filter_map { |arg| arg.name if arg.required }
|
||||
def required_args
|
||||
arguments_value.filter_map { |arg| arg.name if arg.required }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ module ModelContextProtocol
|
|||
|
||||
def initialize(name: "model_context_protocol", tools: [], prompts: [], resources: [], context: nil)
|
||||
@name = name
|
||||
@tools = tools.to_h { |t| [t.name, t] }
|
||||
@prompts = prompts.to_h { |p| [p.name, p] }
|
||||
@tools = tools.to_h { |t| [t.name_value, t] }
|
||||
@prompts = prompts.to_h { |p| [p.name_value, p] }
|
||||
@resources = resources
|
||||
@resource_index = resources.index_by(&:uri)
|
||||
@context = context
|
||||
|
|
@ -39,21 +39,13 @@ module ModelContextProtocol
|
|||
|
||||
def handle(request)
|
||||
JsonRpcHandler.handle(request) do |method|
|
||||
instrument_call(method) do
|
||||
case method
|
||||
when "tools/list"
|
||||
->(params) { { tools: @handlers["tools/list"].call(params) } }
|
||||
when "prompts/list"
|
||||
->(params) { { prompts: @handlers["prompts/list"].call(params) } }
|
||||
when "resources/list"
|
||||
->(params) { { resources: @handlers["resources/list"].call(params) } }
|
||||
else
|
||||
@handlers[method]
|
||||
end
|
||||
rescue => e
|
||||
report_exception(e, { request: request })
|
||||
raise RequestHandlerError.new("Internal error handling #{request[:method]} request", request)
|
||||
end
|
||||
handle_request(request, method)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_json(request)
|
||||
JsonRpcHandler.handle_json(request) do |method|
|
||||
handle_request(request, method)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -83,6 +75,24 @@ module ModelContextProtocol
|
|||
|
||||
private
|
||||
|
||||
def handle_request(request, method)
|
||||
instrument_call(method) do
|
||||
case method
|
||||
when "tools/list"
|
||||
->(params) { { tools: @handlers["tools/list"].call(params) } }
|
||||
when "prompts/list"
|
||||
->(params) { { prompts: @handlers["prompts/list"].call(params) } }
|
||||
when "resources/list"
|
||||
->(params) { { resources: @handlers["resources/list"].call(params) } }
|
||||
else
|
||||
@handlers[method]
|
||||
end
|
||||
rescue => e
|
||||
report_exception(e, { request: request })
|
||||
raise RequestHandlerError.new("Internal error handling #{request[:method]} request", request)
|
||||
end
|
||||
end
|
||||
|
||||
def capabilities
|
||||
@capabilities ||= {
|
||||
prompts: {},
|
||||
|
|
@ -152,7 +162,7 @@ module ModelContextProtocol
|
|||
prompt_args = request[:arguments]
|
||||
prompt.validate_arguments!(prompt_args)
|
||||
|
||||
prompt.template(prompt_args).to_h
|
||||
prompt.template(prompt_args, context:).to_h
|
||||
end
|
||||
|
||||
def list_resources(request)
|
||||
|
|
@ -172,7 +182,6 @@ module ModelContextProtocol
|
|||
end
|
||||
|
||||
add_instrumentation_data(resource_uri:)
|
||||
|
||||
resource.to_h
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -11,14 +11,24 @@ module ModelContextProtocol
|
|||
end
|
||||
|
||||
def to_h
|
||||
{ content:, is_error: }.compact
|
||||
{ content:, isError: is_error }.compact
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
NOT_SET = Object.new
|
||||
|
||||
attr_reader :description_value
|
||||
attr_reader :input_schema_value
|
||||
|
||||
def call(*args, context:)
|
||||
raise NotImplementedError, "Subclasses must implement call"
|
||||
end
|
||||
|
||||
def to_h
|
||||
{ name: name_value, description: description_value, inputSchema: input_schema_value }
|
||||
end
|
||||
|
||||
def inherited(subclass)
|
||||
super
|
||||
subclass.instance_variable_set(:@name_value, nil)
|
||||
|
|
@ -26,45 +36,44 @@ module ModelContextProtocol
|
|||
subclass.instance_variable_set(:@input_schema_value, nil)
|
||||
end
|
||||
|
||||
def tool_name(value)
|
||||
@name_value = value
|
||||
def tool_name(value = NOT_SET)
|
||||
if value == NOT_SET
|
||||
@name_value
|
||||
else
|
||||
@name_value = value
|
||||
end
|
||||
end
|
||||
|
||||
def name_value
|
||||
@name_value || StringUtils.handle_from_class_name(name)
|
||||
end
|
||||
|
||||
def description(value)
|
||||
@description_value = value
|
||||
def description(value = NOT_SET)
|
||||
if value == NOT_SET
|
||||
@description_value
|
||||
else
|
||||
@description_value = value
|
||||
end
|
||||
end
|
||||
|
||||
def input_schema(value)
|
||||
@input_schema_value = value
|
||||
def input_schema(value = NOT_SET)
|
||||
if value == NOT_SET
|
||||
@input_schema_value
|
||||
else
|
||||
@input_schema_value = value
|
||||
end
|
||||
end
|
||||
|
||||
def define(name: nil, description: nil, input_schema: nil, &block)
|
||||
new(name:, description:, input_schema:).tap do |tool|
|
||||
tool.define_singleton_method(:call) do |*args, context:|
|
||||
Class.new(self) do
|
||||
tool_name name
|
||||
description description
|
||||
input_schema input_schema
|
||||
define_singleton_method(:call) do |*args, context:|
|
||||
instance_exec(*args, context:, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :name, :description, :input_schema
|
||||
|
||||
def initialize(name: nil, description: nil, input_schema: nil)
|
||||
@name = name || self.class.name_value
|
||||
@description = description || self.class.description_value
|
||||
@input_schema = input_schema || self.class.input_schema_value
|
||||
end
|
||||
|
||||
def call(*args, context:)
|
||||
raise NotImplementedError, "Subclasses must implement call"
|
||||
end
|
||||
|
||||
def to_h
|
||||
{ name:, description:, inputSchema: input_schema }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ModelContextProtocol
|
||||
VERSION = "0.3.0"
|
||||
VERSION = "0.4.0"
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue