model_context_protocol_riccardo 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.cursor/rules/release-changelogs.mdc +32 -0
- data/.gitattributes +4 -0
- data/.github/workflows/ci.yml +22 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +5 -0
- data/.ruby-version +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +117 -0
- data/LICENSE.txt +21 -0
- data/README.md +473 -0
- data/Rakefile +17 -0
- data/bin/console +15 -0
- data/bin/rake +31 -0
- data/bin/setup +8 -0
- data/dev.yml +31 -0
- data/examples/stdio_server.rb +94 -0
- data/lib/mcp-ruby.rb +3 -0
- data/lib/model_context_protocol/configuration.rb +75 -0
- data/lib/model_context_protocol/content.rb +33 -0
- data/lib/model_context_protocol/instrumentation.rb +26 -0
- data/lib/model_context_protocol/json_rpc.rb +11 -0
- data/lib/model_context_protocol/methods.rb +86 -0
- data/lib/model_context_protocol/prompt/argument.rb +21 -0
- data/lib/model_context_protocol/prompt/message.rb +19 -0
- data/lib/model_context_protocol/prompt/result.rb +19 -0
- data/lib/model_context_protocol/prompt.rb +82 -0
- data/lib/model_context_protocol/resource/contents.rb +45 -0
- data/lib/model_context_protocol/resource/embedded.rb +18 -0
- data/lib/model_context_protocol/resource.rb +24 -0
- data/lib/model_context_protocol/resource_template.rb +24 -0
- data/lib/model_context_protocol/server.rb +258 -0
- data/lib/model_context_protocol/string_utils.rb +26 -0
- data/lib/model_context_protocol/tool/annotations.rb +27 -0
- data/lib/model_context_protocol/tool/input_schema.rb +26 -0
- data/lib/model_context_protocol/tool/response.rb +18 -0
- data/lib/model_context_protocol/tool.rb +85 -0
- data/lib/model_context_protocol/transport.rb +33 -0
- data/lib/model_context_protocol/transports/stdio.rb +33 -0
- data/lib/model_context_protocol/version.rb +5 -0
- data/lib/model_context_protocol.rb +44 -0
- data/model_context_protocol.gemspec +32 -0
- metadata +116 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ModelContextProtocol
|
4
|
+
class Configuration
|
5
|
+
DEFAULT_PROTOCOL_VERSION = "2024-11-05"
|
6
|
+
|
7
|
+
attr_writer :exception_reporter, :instrumentation_callback, :protocol_version
|
8
|
+
|
9
|
+
def initialize(exception_reporter: nil, instrumentation_callback: nil, protocol_version: nil)
|
10
|
+
@exception_reporter = exception_reporter
|
11
|
+
@instrumentation_callback = instrumentation_callback
|
12
|
+
@protocol_version = protocol_version
|
13
|
+
end
|
14
|
+
|
15
|
+
def protocol_version
|
16
|
+
@protocol_version || DEFAULT_PROTOCOL_VERSION
|
17
|
+
end
|
18
|
+
|
19
|
+
def protocol_version?
|
20
|
+
!@protocol_version.nil?
|
21
|
+
end
|
22
|
+
|
23
|
+
def exception_reporter
|
24
|
+
@exception_reporter || default_exception_reporter
|
25
|
+
end
|
26
|
+
|
27
|
+
def exception_reporter?
|
28
|
+
!@exception_reporter.nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
def instrumentation_callback
|
32
|
+
@instrumentation_callback || default_instrumentation_callback
|
33
|
+
end
|
34
|
+
|
35
|
+
def instrumentation_callback?
|
36
|
+
!@instrumentation_callback.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
def merge(other)
|
40
|
+
return self if other.nil?
|
41
|
+
|
42
|
+
exception_reporter = if other.exception_reporter?
|
43
|
+
other.exception_reporter
|
44
|
+
else
|
45
|
+
@exception_reporter
|
46
|
+
end
|
47
|
+
instrumentation_callback = if other.instrumentation_callback?
|
48
|
+
other.instrumentation_callback
|
49
|
+
else
|
50
|
+
@instrumentation_callback
|
51
|
+
end
|
52
|
+
protocol_version = if other.protocol_version?
|
53
|
+
other.protocol_version
|
54
|
+
else
|
55
|
+
@protocol_version
|
56
|
+
end
|
57
|
+
|
58
|
+
Configuration.new(
|
59
|
+
exception_reporter:,
|
60
|
+
instrumentation_callback:,
|
61
|
+
protocol_version:,
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def default_exception_reporter
|
68
|
+
@default_exception_reporter ||= ->(exception, server_context) {}
|
69
|
+
end
|
70
|
+
|
71
|
+
def default_instrumentation_callback
|
72
|
+
@default_instrumentation_callback ||= ->(data) {}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module ModelContextProtocol
|
5
|
+
module Content
|
6
|
+
class Text
|
7
|
+
attr_reader :text, :annotations
|
8
|
+
|
9
|
+
def initialize(text, annotations: nil)
|
10
|
+
@text = text
|
11
|
+
@annotations = annotations
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_h
|
15
|
+
{ text:, annotations:, type: "text" }.compact
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Image
|
20
|
+
attr_reader :data, :mime_type, :annotations
|
21
|
+
|
22
|
+
def initialize(data, mime_type, annotations: nil)
|
23
|
+
@data = data
|
24
|
+
@mime_type = mime_type
|
25
|
+
@annotations = annotations
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_h
|
29
|
+
{ data:, mime_type:, annotations:, type: "image" }.compact
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ModelContextProtocol
|
4
|
+
module Instrumentation
|
5
|
+
def instrument_call(method, &block)
|
6
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
7
|
+
begin
|
8
|
+
@instrumentation_data = {}
|
9
|
+
add_instrumentation_data(method:)
|
10
|
+
|
11
|
+
result = yield block
|
12
|
+
|
13
|
+
result
|
14
|
+
ensure
|
15
|
+
end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
16
|
+
add_instrumentation_data(duration: end_time - start_time)
|
17
|
+
|
18
|
+
configuration.instrumentation_callback.call(@instrumentation_data)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_instrumentation_data(**kwargs)
|
23
|
+
@instrumentation_data.merge!(kwargs)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "model_context_protocol/json_rpc/error"
|
4
|
+
require "model_context_protocol/json_rpc/request"
|
5
|
+
require "model_context_protocol/json_rpc/response"
|
6
|
+
|
7
|
+
module ModelContextProtocol
|
8
|
+
module JsonRPC
|
9
|
+
VERSION = "2.0"
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ModelContextProtocol
|
4
|
+
module Methods
|
5
|
+
INITIALIZE = "initialize"
|
6
|
+
PING = "ping"
|
7
|
+
LOGGING_SET_LEVEL = "logging/setLevel"
|
8
|
+
|
9
|
+
PROMPTS_GET = "prompts/get"
|
10
|
+
PROMPTS_LIST = "prompts/list"
|
11
|
+
COMPLETION_COMPLETE = "completion/complete"
|
12
|
+
|
13
|
+
RESOURCES_LIST = "resources/list"
|
14
|
+
RESOURCES_READ = "resources/read"
|
15
|
+
RESOURCES_TEMPLATES_LIST = "resources/templates/list"
|
16
|
+
RESOURCES_SUBSCRIBE = "resources/subscribe"
|
17
|
+
RESOURCES_UNSUBSCRIBE = "resources/unsubscribe"
|
18
|
+
|
19
|
+
TOOLS_CALL = "tools/call"
|
20
|
+
TOOLS_LIST = "tools/list"
|
21
|
+
|
22
|
+
SAMPLING_CREATE_MESSAGE = "sampling/createMessage"
|
23
|
+
|
24
|
+
class MissingRequiredCapabilityError < StandardError
|
25
|
+
attr_reader :method
|
26
|
+
attr_reader :capability
|
27
|
+
|
28
|
+
def initialize(method, capability)
|
29
|
+
super("Server does not support #{capability} (required for #{method})")
|
30
|
+
@method = method
|
31
|
+
@capability = capability
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
extend self
|
36
|
+
|
37
|
+
ALL_METHODS = [
|
38
|
+
SAMPLING_CREATE_MESSAGE,
|
39
|
+
INITIALIZE,
|
40
|
+
LOGGING_SET_LEVEL,
|
41
|
+
PING,
|
42
|
+
PROMPTS_GET,
|
43
|
+
PROMPTS_LIST,
|
44
|
+
COMPLETION_COMPLETE,
|
45
|
+
RESOURCES_LIST,
|
46
|
+
RESOURCES_READ,
|
47
|
+
RESOURCES_TEMPLATES_LIST,
|
48
|
+
RESOURCES_SUBSCRIBE,
|
49
|
+
RESOURCES_UNSUBSCRIBE,
|
50
|
+
TOOLS_CALL,
|
51
|
+
TOOLS_LIST,
|
52
|
+
]
|
53
|
+
|
54
|
+
def ensure_capability!(method, capabilities)
|
55
|
+
case method
|
56
|
+
when PROMPTS_GET, PROMPTS_LIST, COMPLETION_COMPLETE
|
57
|
+
unless capabilities[:prompts]
|
58
|
+
raise MissingRequiredCapabilityError.new(method, :prompts)
|
59
|
+
end
|
60
|
+
when RESOURCES_LIST, RESOURCES_TEMPLATES_LIST, RESOURCES_READ, RESOURCES_SUBSCRIBE, RESOURCES_UNSUBSCRIBE
|
61
|
+
unless capabilities[:resources]
|
62
|
+
raise MissingRequiredCapabilityError.new(method, :resources)
|
63
|
+
end
|
64
|
+
|
65
|
+
if method == RESOURCES_SUBSCRIBE && !capabilities[:resources][:subscribe]
|
66
|
+
raise MissingRequiredCapabilityError.new(method, :resources_subscribe)
|
67
|
+
end
|
68
|
+
when TOOLS_CALL, TOOLS_LIST
|
69
|
+
unless capabilities[:tools]
|
70
|
+
raise MissingRequiredCapabilityError.new(method, :tools)
|
71
|
+
end
|
72
|
+
when SAMPLING_CREATE_MESSAGE
|
73
|
+
unless capabilities[:sampling]
|
74
|
+
raise MissingRequiredCapabilityError.new(method, :sampling)
|
75
|
+
end
|
76
|
+
when LOGGING_SET_LEVEL
|
77
|
+
# Logging is unsupported by the Server
|
78
|
+
unless capabilities[:logging]
|
79
|
+
raise MissingRequiredCapabilityError.new(method, :logging)
|
80
|
+
end
|
81
|
+
when INITIALIZE, PING
|
82
|
+
# No specific capability required for initialize or ping
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module ModelContextProtocol
|
5
|
+
class Prompt
|
6
|
+
class Argument
|
7
|
+
attr_reader :name, :description, :required, :arguments
|
8
|
+
|
9
|
+
def initialize(name:, description: nil, required: false)
|
10
|
+
@name = name
|
11
|
+
@description = description
|
12
|
+
@required = required
|
13
|
+
@arguments = arguments
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_h
|
17
|
+
{ name:, description:, required: }.compact
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module ModelContextProtocol
|
5
|
+
class Prompt
|
6
|
+
class Message
|
7
|
+
attr_reader :role, :content
|
8
|
+
|
9
|
+
def initialize(role:, content:)
|
10
|
+
@role = role
|
11
|
+
@content = content
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_h
|
15
|
+
{ role:, content: content.to_h }.compact
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module ModelContextProtocol
|
5
|
+
class Prompt
|
6
|
+
class Result
|
7
|
+
attr_reader :description, :messages
|
8
|
+
|
9
|
+
def initialize(description: nil, messages: [])
|
10
|
+
@description = description
|
11
|
+
@messages = messages
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_h
|
15
|
+
{ description:, messages: messages.map(&:to_h) }.compact
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module ModelContextProtocol
|
5
|
+
class Prompt
|
6
|
+
class << self
|
7
|
+
NOT_SET = Object.new
|
8
|
+
|
9
|
+
attr_reader :description_value
|
10
|
+
attr_reader :arguments_value
|
11
|
+
|
12
|
+
def template(args, server_context:)
|
13
|
+
raise NotImplementedError, "Subclasses must implement template"
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_h
|
17
|
+
{ name: name_value, description: description_value, arguments: arguments_value.map(&:to_h) }.compact
|
18
|
+
end
|
19
|
+
|
20
|
+
def inherited(subclass)
|
21
|
+
super
|
22
|
+
subclass.instance_variable_set(:@name_value, nil)
|
23
|
+
subclass.instance_variable_set(:@description_value, nil)
|
24
|
+
subclass.instance_variable_set(:@arguments_value, nil)
|
25
|
+
end
|
26
|
+
|
27
|
+
def prompt_name(value = NOT_SET)
|
28
|
+
if value == NOT_SET
|
29
|
+
@name_value
|
30
|
+
else
|
31
|
+
@name_value = value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def name_value
|
36
|
+
@name_value || StringUtils.handle_from_class_name(name)
|
37
|
+
end
|
38
|
+
|
39
|
+
def description(value = NOT_SET)
|
40
|
+
if value == NOT_SET
|
41
|
+
@description_value
|
42
|
+
else
|
43
|
+
@description_value = value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def arguments(value = NOT_SET)
|
48
|
+
if value == NOT_SET
|
49
|
+
@arguments_value
|
50
|
+
else
|
51
|
+
@arguments_value = value
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def define(name: nil, description: nil, arguments: [], &block)
|
56
|
+
Class.new(self) do
|
57
|
+
prompt_name name
|
58
|
+
description description
|
59
|
+
arguments arguments
|
60
|
+
define_singleton_method(:template) do |args, server_context:|
|
61
|
+
instance_exec(args, server_context:, &block)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def validate_arguments!(args)
|
67
|
+
missing = required_args - args.keys
|
68
|
+
return if missing.empty?
|
69
|
+
|
70
|
+
raise ModelContextProtocol::Server::RequestHandlerError.new(
|
71
|
+
"Missing required arguments: #{missing.join(", ")}", nil, error_type: :missing_required_arguments
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def required_args
|
78
|
+
arguments_value.filter_map { |arg| arg.name.to_sym if arg.required }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module ModelContextProtocol
|
5
|
+
class Resource
|
6
|
+
class Contents
|
7
|
+
attr_reader :uri, :mime_type
|
8
|
+
|
9
|
+
def initialize(uri:, mime_type: nil)
|
10
|
+
@uri = uri
|
11
|
+
@mime_type = mime_type
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_h
|
15
|
+
{ uri:, mime_type: }.compact
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class TextContents < Contents
|
20
|
+
attr_reader :text
|
21
|
+
|
22
|
+
def initialize(text:, uri:, mime_type:)
|
23
|
+
super(uri: uri, mime_type: mime_type)
|
24
|
+
@text = text
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_h
|
28
|
+
super.merge(text: text)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class BlobContents < Contents
|
33
|
+
attr_reader :data
|
34
|
+
|
35
|
+
def initialize(data:, uri:, mime_type:)
|
36
|
+
super(uri: uri, mime_type: mime_type)
|
37
|
+
@data = data
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_h
|
41
|
+
super.merge(data: data)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module ModelContextProtocol
|
5
|
+
class Resource
|
6
|
+
class Embedded
|
7
|
+
attr_reader :resource, :annotations
|
8
|
+
|
9
|
+
def initialize(resource:, annotations: nil)
|
10
|
+
@annotations = annotations
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_h
|
14
|
+
{ resource: resource.to_h, annotations: }.compact
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module ModelContextProtocol
|
5
|
+
class Resource
|
6
|
+
attr_reader :uri, :name, :description, :mime_type
|
7
|
+
|
8
|
+
def initialize(uri:, name:, description:, mime_type:)
|
9
|
+
@uri = uri
|
10
|
+
@name = name
|
11
|
+
@description = description
|
12
|
+
@mime_type = mime_type
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_h
|
16
|
+
{
|
17
|
+
uri: @uri,
|
18
|
+
name: @name,
|
19
|
+
description: @description,
|
20
|
+
mimeType: @mime_type,
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module ModelContextProtocol
|
5
|
+
class ResourceTemplate
|
6
|
+
attr_reader :uri_template, :name, :description, :mime_type
|
7
|
+
|
8
|
+
def initialize(uri_template:, name:, description: nil, mime_type: nil)
|
9
|
+
@uri_template = uri_template
|
10
|
+
@name = name
|
11
|
+
@description = description
|
12
|
+
@mime_type = mime_type
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_h
|
16
|
+
{
|
17
|
+
uriTemplate: @uri_template,
|
18
|
+
name: @name,
|
19
|
+
description: @description,
|
20
|
+
mimeType: @mime_type,
|
21
|
+
}.compact
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|