model-context-protocol-rb 0.2.0 → 0.3.1
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 +4 -4
- data/.solargraph.yml +13 -0
- data/CHANGELOG.md +26 -1
- data/README.md +275 -19
- data/Rakefile +2 -0
- data/lib/model_context_protocol/server/configuration.rb +78 -0
- data/lib/model_context_protocol/server/prompt.rb +72 -0
- data/lib/model_context_protocol/server/registry.rb +102 -0
- data/lib/model_context_protocol/server/resource.rb +67 -0
- data/lib/model_context_protocol/server/router.rb +25 -106
- data/lib/model_context_protocol/server/stdio_transport.rb +65 -0
- data/lib/model_context_protocol/server/tool.rb +107 -0
- data/lib/model_context_protocol/server.rb +89 -55
- data/lib/model_context_protocol/version.rb +1 -1
- data/tasks/mcp.rake +62 -0
- data/tasks/templates/dev.erb +33 -0
- metadata +26 -8
- data/lib/model_context_protocol/server/router/base_map.rb +0 -22
- data/lib/model_context_protocol/server/router/prompts_map.rb +0 -17
- data/lib/model_context_protocol/server/router/protocol_map.rb +0 -0
- data/lib/model_context_protocol/server/router/resources_map.rb +0 -17
- data/lib/model_context_protocol/server/router/tools_map.rb +0 -17
@@ -0,0 +1,67 @@
|
|
1
|
+
module ModelContextProtocol
|
2
|
+
class Server::Resource
|
3
|
+
attr_reader :mime_type, :uri
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@mime_type = self.class.mime_type
|
7
|
+
@uri = self.class.uri
|
8
|
+
end
|
9
|
+
|
10
|
+
def call
|
11
|
+
raise NotImplementedError, "Subclasses must implement the call method"
|
12
|
+
end
|
13
|
+
|
14
|
+
TextResponse = Data.define(:resource, :text) do
|
15
|
+
def serialized
|
16
|
+
{contents: [{mimeType: resource.mime_type, text:, uri: resource.uri}]}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
private_constant :TextResponse
|
20
|
+
|
21
|
+
BinaryResponse = Data.define(:blob, :resource) do
|
22
|
+
def serialized
|
23
|
+
{contents: [{blob:, mimeType: resource.mime_type, uri: resource.uri}]}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
private_constant :BinaryResponse
|
27
|
+
|
28
|
+
private def respond_with(type, **options)
|
29
|
+
case [type, options]
|
30
|
+
in [:text, {text:}]
|
31
|
+
TextResponse[resource: self, text:]
|
32
|
+
in [:binary, {blob:}]
|
33
|
+
BinaryResponse[blob:, resource: self]
|
34
|
+
else
|
35
|
+
raise ModelContextProtocol::Server::ResponseArgumentsError, "Invalid arguments: #{type}, #{options}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
attr_reader :name, :description, :mime_type, :uri
|
41
|
+
|
42
|
+
def with_metadata(&block)
|
43
|
+
metadata = instance_eval(&block)
|
44
|
+
|
45
|
+
@name = metadata[:name]
|
46
|
+
@description = metadata[:description]
|
47
|
+
@mime_type = metadata[:mime_type]
|
48
|
+
@uri = metadata[:uri]
|
49
|
+
end
|
50
|
+
|
51
|
+
def inherited(subclass)
|
52
|
+
subclass.instance_variable_set(:@name, @name)
|
53
|
+
subclass.instance_variable_set(:@description, @description)
|
54
|
+
subclass.instance_variable_set(:@mime_type, @mime_type)
|
55
|
+
subclass.instance_variable_set(:@uri, @uri)
|
56
|
+
end
|
57
|
+
|
58
|
+
def call
|
59
|
+
new.call
|
60
|
+
end
|
61
|
+
|
62
|
+
def metadata
|
63
|
+
{name: @name, description: @description, mime_type: @mime_type, uri: @uri}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -1,117 +1,36 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
module ModelContextProtocol
|
4
|
-
class Server
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
def self.new(&block)
|
9
|
-
router = allocate
|
10
|
-
router.send(:initialize)
|
11
|
-
router.instance_eval(&block) if block
|
12
|
-
router
|
13
|
-
end
|
14
|
-
|
15
|
-
def initialize
|
16
|
-
@routes = {}
|
17
|
-
register_protocol_routes
|
18
|
-
end
|
19
|
-
|
20
|
-
def prompts(&block)
|
21
|
-
PromptsMap.new(@routes).instance_eval(&block)
|
22
|
-
end
|
23
|
-
|
24
|
-
def resources(&block)
|
25
|
-
ResourcesMap.new(@routes).instance_eval(&block)
|
26
|
-
end
|
27
|
-
|
28
|
-
def tools(&block)
|
29
|
-
ToolsMap.new(@routes).instance_eval(&block)
|
30
|
-
end
|
31
|
-
|
32
|
-
def route(message)
|
33
|
-
handler_config = @routes[message["method"]]
|
34
|
-
return nil unless handler_config
|
35
|
-
|
36
|
-
handler_config[:handler].call(message)
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
|
41
|
-
def server_info
|
42
|
-
if server&.configuration
|
43
|
-
{
|
44
|
-
name: server.configuration.name,
|
45
|
-
version: server.configuration.version
|
46
|
-
}
|
47
|
-
else
|
48
|
-
{name: "mcp-server", version: "1.0.0"}
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def register_protocol_routes
|
53
|
-
register("initialize") do |_message|
|
54
|
-
{
|
55
|
-
protocolVersion: ModelContextProtocol::Server::PROTOCOL_VERSION,
|
56
|
-
capabilities: build_capabilities,
|
57
|
-
serverInfo: server_info
|
58
|
-
}
|
59
|
-
end
|
2
|
+
class Server::Router
|
3
|
+
# Raised when an invalid method is provided.
|
4
|
+
class MethodNotFoundError < StandardError; end
|
60
5
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
register("ping") do |_message|
|
66
|
-
{} # Simple pong response
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def register(method, handler = nil, **options, &block)
|
71
|
-
@routes[method] = {
|
72
|
-
handler: block || handler,
|
73
|
-
options: options
|
74
|
-
}
|
75
|
-
end
|
76
|
-
|
77
|
-
def build_capabilities
|
78
|
-
{
|
79
|
-
prompts: has_prompt_routes? ? {broadcast_changes: prompt_broadcasts_changes?} : nil,
|
80
|
-
resources: has_resource_routes? ? {
|
81
|
-
broadcast_changes: resource_broadcasts_changes?,
|
82
|
-
subscribe: resource_allows_subscriptions?
|
83
|
-
} : nil,
|
84
|
-
tools: has_tool_routes? ? {broadcast_changes: tool_broadcasts_changes?} : nil
|
85
|
-
}.compact
|
86
|
-
end
|
87
|
-
|
88
|
-
def has_prompt_routes?
|
89
|
-
@routes.key?("prompts/list") || @routes.key?("prompts/get")
|
90
|
-
end
|
91
|
-
|
92
|
-
def prompt_broadcasts_changes?
|
93
|
-
@routes.dig("prompts/list", :options, :broadcast_changes)
|
94
|
-
end
|
6
|
+
def initialize(configuration: nil)
|
7
|
+
@handlers = {}
|
8
|
+
@configuration = configuration
|
9
|
+
end
|
95
10
|
|
96
|
-
|
97
|
-
|
98
|
-
|
11
|
+
def map(method, &handler)
|
12
|
+
@handlers[method] = handler
|
13
|
+
end
|
99
14
|
|
100
|
-
|
101
|
-
|
102
|
-
|
15
|
+
def route(message)
|
16
|
+
method = message["method"]
|
17
|
+
handler = @handlers[method]
|
18
|
+
raise MethodNotFoundError, "Method not found: #{method}" unless handler
|
103
19
|
|
104
|
-
|
105
|
-
|
20
|
+
with_environment(@configuration&.environment_variables) do
|
21
|
+
handler.call(message)
|
106
22
|
end
|
23
|
+
end
|
107
24
|
|
108
|
-
|
109
|
-
@routes.key?("tools/list") || @routes.key?("tools/call")
|
110
|
-
end
|
25
|
+
private
|
111
26
|
|
112
|
-
|
113
|
-
|
114
|
-
|
27
|
+
def with_environment(vars)
|
28
|
+
original = ENV.to_h
|
29
|
+
vars&.each { |key, value| ENV[key] = value }
|
30
|
+
yield
|
31
|
+
ensure
|
32
|
+
ENV.clear
|
33
|
+
original.each { |key, value| ENV[key] = value }
|
115
34
|
end
|
116
35
|
end
|
117
36
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module ModelContextProtocol
|
2
|
+
class Server::StdioTransport
|
3
|
+
Response = Data.define(:id, :result) do
|
4
|
+
def serialized
|
5
|
+
{jsonrpc: "2.0", id:, result:}
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
ErrorResponse = Data.define(:id, :error) do
|
10
|
+
def serialized
|
11
|
+
{jsonrpc: "2.0", id:, error:}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :logger, :router
|
16
|
+
|
17
|
+
def initialize(logger:, router:)
|
18
|
+
@logger = logger
|
19
|
+
@router = router
|
20
|
+
end
|
21
|
+
|
22
|
+
def begin
|
23
|
+
loop do
|
24
|
+
line = $stdin.gets
|
25
|
+
break unless line
|
26
|
+
|
27
|
+
begin
|
28
|
+
message = JSON.parse(line.chomp)
|
29
|
+
next if message["method"].start_with?("notifications")
|
30
|
+
|
31
|
+
result = router.route(message)
|
32
|
+
send_message(Response[id: message["id"], result: result.serialized])
|
33
|
+
rescue ModelContextProtocol::Server::ParameterValidationError => validation_error
|
34
|
+
log("Validation error: #{validation_error.message}")
|
35
|
+
send_message(
|
36
|
+
ErrorResponse[id: message["id"], error: {code: -32602, message: validation_error.message}]
|
37
|
+
)
|
38
|
+
rescue JSON::ParserError => parser_error
|
39
|
+
log("Parser error: #{parser_error.message}")
|
40
|
+
send_message(
|
41
|
+
ErrorResponse[id: "", error: {code: -32700, message: parser_error.message}]
|
42
|
+
)
|
43
|
+
rescue => error
|
44
|
+
log("Internal error: #{error.message}")
|
45
|
+
log(error.backtrace)
|
46
|
+
send_message(
|
47
|
+
ErrorResponse[id: message["id"], error: {code: -32603, message: error.message}]
|
48
|
+
)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def log(output, level = :error)
|
56
|
+
logger.send(level.to_sym, output)
|
57
|
+
end
|
58
|
+
|
59
|
+
def send_message(message)
|
60
|
+
message_json = JSON.generate(message.serialized)
|
61
|
+
$stdout.puts(message_json)
|
62
|
+
$stdout.flush
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require "json-schema"
|
2
|
+
|
3
|
+
module ModelContextProtocol
|
4
|
+
class Server::Tool
|
5
|
+
attr_reader :params
|
6
|
+
|
7
|
+
def initialize(params)
|
8
|
+
validate!(params)
|
9
|
+
@params = params
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
raise NotImplementedError, "Subclasses must implement the call method"
|
14
|
+
end
|
15
|
+
|
16
|
+
TextResponse = Data.define(:text) do
|
17
|
+
def serialized
|
18
|
+
{content: [{type: "text", text:}], isError: false}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
private_constant :TextResponse
|
22
|
+
|
23
|
+
ImageResponse = Data.define(:data, :mime_type) do
|
24
|
+
def initialize(data:, mime_type: "image/png")
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
def serialized
|
29
|
+
{content: [{type: "image", data:, mimeType: mime_type}], isError: false}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
private_constant :ImageResponse
|
33
|
+
|
34
|
+
ResourceResponse = Data.define(:uri, :text, :mime_type) do
|
35
|
+
def initialize(uri:, text:, mime_type: "text/plain")
|
36
|
+
super
|
37
|
+
end
|
38
|
+
|
39
|
+
def serialized
|
40
|
+
{content: [{type: "resource", resource: {uri:, mimeType: mime_type, text:}}], isError: false}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
private_constant :ResourceResponse
|
44
|
+
|
45
|
+
ToolErrorResponse = Data.define(:text) do
|
46
|
+
def serialized
|
47
|
+
{content: [{type: "text", text:}], isError: true}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
private_constant :ToolErrorResponse
|
51
|
+
|
52
|
+
private def respond_with(type, **options)
|
53
|
+
case [type, options]
|
54
|
+
in [:text, {text:}]
|
55
|
+
TextResponse[text:]
|
56
|
+
in [:image, {data:, mime_type:}]
|
57
|
+
ImageResponse[data:, mime_type:]
|
58
|
+
in [:image, {data:}]
|
59
|
+
ImageResponse[data:]
|
60
|
+
in [:resource, {mime_type:, text:, uri:}]
|
61
|
+
ResourceResponse[mime_type:, text:, uri:]
|
62
|
+
in [:resource, {text:, uri:}]
|
63
|
+
ResourceResponse[text:, uri:]
|
64
|
+
in [:error, {text:}]
|
65
|
+
ToolErrorResponse[text:]
|
66
|
+
else
|
67
|
+
raise ModelContextProtocol::Server::ResponseArgumentsError, "Invalid arguments: #{type}, #{options}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private def validate!(params)
|
72
|
+
JSON::Validator.validate!(self.class.input_schema, params)
|
73
|
+
end
|
74
|
+
|
75
|
+
class << self
|
76
|
+
attr_reader :name, :description, :input_schema
|
77
|
+
|
78
|
+
def with_metadata(&block)
|
79
|
+
metadata = instance_eval(&block)
|
80
|
+
|
81
|
+
@name = metadata[:name]
|
82
|
+
@description = metadata[:description]
|
83
|
+
@input_schema = metadata[:inputSchema]
|
84
|
+
end
|
85
|
+
|
86
|
+
def inherited(subclass)
|
87
|
+
subclass.instance_variable_set(:@name, @name)
|
88
|
+
subclass.instance_variable_set(:@description, @description)
|
89
|
+
subclass.instance_variable_set(:@input_schema, @input_schema)
|
90
|
+
end
|
91
|
+
|
92
|
+
def call(params)
|
93
|
+
new(params).call
|
94
|
+
rescue JSON::Schema::ValidationError => validation_error
|
95
|
+
raise ModelContextProtocol::Server::ParameterValidationError, validation_error.message
|
96
|
+
rescue ModelContextProtocol::Server::ResponseArgumentsError => response_arguments_error
|
97
|
+
raise response_arguments_error
|
98
|
+
rescue => error
|
99
|
+
ToolErrorResponse[text: error.message]
|
100
|
+
end
|
101
|
+
|
102
|
+
def metadata
|
103
|
+
{name: @name, description: @description, inputSchema: @input_schema}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -1,81 +1,115 @@
|
|
1
|
-
require "
|
1
|
+
require "logger"
|
2
2
|
|
3
3
|
module ModelContextProtocol
|
4
4
|
class Server
|
5
|
-
|
6
|
-
|
5
|
+
# Raised when invalid response arguments are provided.
|
6
|
+
class ResponseArgumentsError < StandardError; end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
end
|
8
|
+
# Raised when invalid parameters are provided.
|
9
|
+
class ParameterValidationError < StandardError; end
|
11
10
|
|
12
|
-
|
13
|
-
raise InvalidServerNameError unless valid_name?
|
14
|
-
raise InvalidRouterError unless valid_router?
|
15
|
-
raise InvalidServerVersionError unless valid_version?
|
16
|
-
end
|
11
|
+
attr_reader :configuration, :router
|
17
12
|
|
18
|
-
|
13
|
+
def initialize
|
14
|
+
@configuration = Configuration.new
|
15
|
+
yield(@configuration) if block_given?
|
16
|
+
@router = Router.new(configuration:)
|
17
|
+
map_handlers
|
18
|
+
end
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
def start
|
21
|
+
configuration.validate!
|
22
|
+
logdev = configuration.logging_enabled? ? $stderr : File::NULL
|
23
|
+
StdioTransport.new(logger: Logger.new(logdev), router:).begin
|
24
|
+
end
|
23
25
|
|
24
|
-
|
25
|
-
true
|
26
|
-
end
|
26
|
+
private
|
27
27
|
|
28
|
-
|
29
|
-
|
28
|
+
PROTOCOL_VERSION = "2024-11-05".freeze
|
29
|
+
private_constant :PROTOCOL_VERSION
|
30
|
+
|
31
|
+
InitializeResponse = Data.define(:protocol_version, :capabilities, :server_info) do
|
32
|
+
def serialized
|
33
|
+
{
|
34
|
+
protocolVersion: protocol_version,
|
35
|
+
capabilities: capabilities,
|
36
|
+
serverInfo: server_info
|
37
|
+
}
|
30
38
|
end
|
31
39
|
end
|
32
40
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
def initialize
|
38
|
-
@configuration = Configuration.new
|
39
|
-
yield(@configuration) if block_given?
|
40
|
-
@configuration.router.server = self if @configuration.router
|
41
|
+
PingResponse = Data.define do
|
42
|
+
def serialized
|
43
|
+
{}
|
44
|
+
end
|
41
45
|
end
|
42
46
|
|
43
|
-
def
|
44
|
-
|
47
|
+
def map_handlers
|
48
|
+
router.map("initialize") do |_message|
|
49
|
+
InitializeResponse[
|
50
|
+
protocol_version: PROTOCOL_VERSION,
|
51
|
+
capabilities: build_capabilities,
|
52
|
+
server_info: {
|
53
|
+
name: configuration.name,
|
54
|
+
version: configuration.version
|
55
|
+
}
|
56
|
+
]
|
57
|
+
end
|
45
58
|
|
46
|
-
|
59
|
+
router.map("ping") do
|
60
|
+
PingResponse[]
|
61
|
+
end
|
47
62
|
|
48
|
-
|
49
|
-
|
50
|
-
|
63
|
+
router.map("resources/list") do
|
64
|
+
configuration.registry.resources_data
|
65
|
+
end
|
51
66
|
|
52
|
-
|
53
|
-
|
67
|
+
router.map("resources/read") do |message|
|
68
|
+
configuration.registry.find_resource(message["params"]["uri"]).call
|
69
|
+
end
|
54
70
|
|
55
|
-
|
56
|
-
|
71
|
+
router.map("prompts/list") do
|
72
|
+
configuration.registry.prompts_data
|
57
73
|
end
|
58
|
-
rescue => e
|
59
|
-
log("Error: #{e.message}")
|
60
|
-
log(e.backtrace)
|
61
|
-
end
|
62
74
|
|
63
|
-
|
75
|
+
router.map("prompts/get") do |message|
|
76
|
+
configuration.registry.find_prompt(message["params"]["name"]).call(message["params"]["arguments"])
|
77
|
+
end
|
64
78
|
|
65
|
-
|
66
|
-
|
67
|
-
|
79
|
+
router.map("tools/list") do
|
80
|
+
configuration.registry.tools_data
|
81
|
+
end
|
68
82
|
|
69
|
-
|
70
|
-
|
83
|
+
router.map("tools/call") do |message|
|
84
|
+
configuration.registry.find_tool(message["params"]["name"]).call(message["params"]["arguments"])
|
85
|
+
end
|
86
|
+
end
|
71
87
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
88
|
+
def build_capabilities
|
89
|
+
{}.tap do |capabilities|
|
90
|
+
capabilities[:logging] = {} if configuration.logging_enabled?
|
91
|
+
|
92
|
+
registry = configuration.registry
|
93
|
+
|
94
|
+
if registry.prompts_options.any? && !registry.instance_variable_get(:@prompts).empty?
|
95
|
+
capabilities[:prompts] = {
|
96
|
+
listChanged: registry.prompts_options[:list_changed]
|
97
|
+
}.compact
|
98
|
+
end
|
99
|
+
|
100
|
+
if registry.resources_options.any? && !registry.instance_variable_get(:@resources).empty?
|
101
|
+
capabilities[:resources] = {
|
102
|
+
subscribe: registry.resources_options[:subscribe],
|
103
|
+
listChanged: registry.resources_options[:list_changed]
|
104
|
+
}.compact
|
105
|
+
end
|
106
|
+
|
107
|
+
if registry.tools_options.any? && !registry.instance_variable_get(:@tools).empty?
|
108
|
+
capabilities[:tools] = {
|
109
|
+
listChanged: registry.tools_options[:list_changed]
|
110
|
+
}.compact
|
111
|
+
end
|
112
|
+
end
|
79
113
|
end
|
80
114
|
end
|
81
115
|
end
|
data/tasks/mcp.rake
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
|
3
|
+
namespace :mcp do
|
4
|
+
desc "Generate the development server executable with the correct Ruby path"
|
5
|
+
task :generate_executable do
|
6
|
+
destination_path = "bin/dev"
|
7
|
+
template_path = File.expand_path("templates/dev.erb", __dir__)
|
8
|
+
|
9
|
+
# Create directory if it doesn't exist
|
10
|
+
FileUtils.mkdir_p(File.dirname(destination_path))
|
11
|
+
|
12
|
+
# Get the Ruby path
|
13
|
+
ruby_path = detect_ruby_path
|
14
|
+
|
15
|
+
# Read and process the template
|
16
|
+
template = File.read(template_path)
|
17
|
+
content = template.gsub("<%= @ruby_path %>", ruby_path)
|
18
|
+
|
19
|
+
# Write the executable
|
20
|
+
File.write(destination_path, content)
|
21
|
+
|
22
|
+
# Set permissions
|
23
|
+
FileUtils.chmod(0o755, destination_path)
|
24
|
+
|
25
|
+
# Show success message
|
26
|
+
puts "\nCreated executable at: #{File.expand_path(destination_path)}"
|
27
|
+
puts "Using Ruby path: #{ruby_path}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def detect_ruby_path
|
31
|
+
# Get Ruby version from project config
|
32
|
+
ruby_version = get_project_ruby_version
|
33
|
+
|
34
|
+
if ruby_version && ruby_version.strip != ""
|
35
|
+
# Find the absolute path to the Ruby executable via ASDF
|
36
|
+
asdf_ruby_path = `asdf where ruby #{ruby_version}`.strip
|
37
|
+
|
38
|
+
if asdf_ruby_path && !asdf_ruby_path.empty? && File.directory?(asdf_ruby_path)
|
39
|
+
return File.join(asdf_ruby_path, "bin", "ruby")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Fallback to current Ruby
|
44
|
+
`which ruby`.strip
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_project_ruby_version
|
48
|
+
# Try ASDF first
|
49
|
+
if File.exist?(".tool-versions")
|
50
|
+
content = File.read(".tool-versions")
|
51
|
+
ruby_line = content.lines.find { |line| line.start_with?("ruby ") }
|
52
|
+
return ruby_line.split[1].strip if ruby_line
|
53
|
+
end
|
54
|
+
|
55
|
+
# Try .ruby-version file
|
56
|
+
if File.exist?(".ruby-version")
|
57
|
+
return File.read(".ruby-version").strip
|
58
|
+
end
|
59
|
+
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env <%= @ruby_path %>
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require_relative "../lib/model_context_protocol"
|
5
|
+
|
6
|
+
Dir[File.join(__dir__, "../spec/support/**/*.rb")].each { |file| require file }
|
7
|
+
|
8
|
+
server = ModelContextProtocol::Server.new do |config|
|
9
|
+
config.name = "MCP Development Server"
|
10
|
+
config.version = "1.0.0"
|
11
|
+
config.enable_log = true
|
12
|
+
config.registry = ModelContextProtocol::Server::Registry.new do
|
13
|
+
prompts list_changed: true do
|
14
|
+
register TestPrompt
|
15
|
+
end
|
16
|
+
|
17
|
+
resources list_changed: true, subscribe: true do
|
18
|
+
register TestResource
|
19
|
+
register TestBinaryResource
|
20
|
+
end
|
21
|
+
|
22
|
+
tools list_changed: true do
|
23
|
+
register TestToolWithTextResponse
|
24
|
+
register TestToolWithImageResponse
|
25
|
+
register TestToolWithImageResponseDefaultMimeType
|
26
|
+
register TestToolWithResourceResponse
|
27
|
+
register TestToolWithResourceResponseDefaultMimeType
|
28
|
+
register TestToolWithToolErrorResponse
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
server.start
|