mcp 0.2.0 → 0.3.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 +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +26 -0
- data/Gemfile +7 -4
- data/README.md +330 -47
- data/examples/http_client.rb +13 -13
- data/examples/http_server.rb +14 -11
- data/examples/stdio_server.rb +4 -3
- data/examples/streamable_http_client.rb +10 -12
- data/examples/streamable_http_server.rb +32 -31
- data/lib/mcp/client/http.rb +88 -0
- data/lib/mcp/client/tool.rb +16 -0
- data/lib/mcp/client.rb +88 -0
- data/lib/mcp/configuration.rb +6 -1
- data/lib/mcp/prompt.rb +13 -2
- data/lib/mcp/resource.rb +8 -6
- data/lib/mcp/resource_template.rb +8 -6
- data/lib/mcp/server/transports/streamable_http_transport.rb +13 -1
- data/lib/mcp/server.rb +41 -12
- data/lib/mcp/tool/annotations.rb +4 -4
- data/lib/mcp/tool/input_schema.rb +6 -1
- data/lib/mcp/tool/output_schema.rb +66 -0
- data/lib/mcp/tool/response.rb +15 -4
- data/lib/mcp/tool.rb +37 -6
- data/lib/mcp/version.rb +1 -1
- data/lib/mcp.rb +4 -0
- metadata +6 -2
data/examples/http_client.rb
CHANGED
@@ -50,7 +50,7 @@ class MCPHTTPClient
|
|
50
50
|
},
|
51
51
|
})
|
52
52
|
puts "Response: #{JSON.pretty_generate(result)}"
|
53
|
-
|
53
|
+
|
54
54
|
result
|
55
55
|
end
|
56
56
|
|
@@ -58,7 +58,7 @@ class MCPHTTPClient
|
|
58
58
|
puts "=== Sending ping ==="
|
59
59
|
result = send_request("ping")
|
60
60
|
puts "Response: #{JSON.pretty_generate(result)}"
|
61
|
-
|
61
|
+
|
62
62
|
result
|
63
63
|
end
|
64
64
|
|
@@ -66,7 +66,7 @@ class MCPHTTPClient
|
|
66
66
|
puts "=== Listing tools ==="
|
67
67
|
result = send_request("tools/list")
|
68
68
|
puts "Response: #{JSON.pretty_generate(result)}"
|
69
|
-
|
69
|
+
|
70
70
|
result
|
71
71
|
end
|
72
72
|
|
@@ -77,7 +77,7 @@ class MCPHTTPClient
|
|
77
77
|
arguments: arguments,
|
78
78
|
})
|
79
79
|
puts "Response: #{JSON.pretty_generate(result)}"
|
80
|
-
|
80
|
+
|
81
81
|
result
|
82
82
|
end
|
83
83
|
|
@@ -85,7 +85,7 @@ class MCPHTTPClient
|
|
85
85
|
puts "=== Listing prompts ==="
|
86
86
|
result = send_request("prompts/list")
|
87
87
|
puts "Response: #{JSON.pretty_generate(result)}"
|
88
|
-
|
88
|
+
|
89
89
|
result
|
90
90
|
end
|
91
91
|
|
@@ -96,7 +96,7 @@ class MCPHTTPClient
|
|
96
96
|
arguments: arguments,
|
97
97
|
})
|
98
98
|
puts "Response: #{JSON.pretty_generate(result)}"
|
99
|
-
|
99
|
+
|
100
100
|
result
|
101
101
|
end
|
102
102
|
|
@@ -104,7 +104,7 @@ class MCPHTTPClient
|
|
104
104
|
puts "=== Listing resources ==="
|
105
105
|
result = send_request("resources/list")
|
106
106
|
puts "Response: #{JSON.pretty_generate(result)}"
|
107
|
-
|
107
|
+
|
108
108
|
result
|
109
109
|
end
|
110
110
|
|
@@ -114,7 +114,7 @@ class MCPHTTPClient
|
|
114
114
|
uri: uri,
|
115
115
|
})
|
116
116
|
puts "Response: #{JSON.pretty_generate(result)}"
|
117
|
-
|
117
|
+
|
118
118
|
result
|
119
119
|
end
|
120
120
|
|
@@ -131,7 +131,6 @@ class MCPHTTPClient
|
|
131
131
|
response = http.request(request)
|
132
132
|
result = JSON.parse(response.body)
|
133
133
|
puts "Response: #{JSON.pretty_generate(result)}"
|
134
|
-
puts
|
135
134
|
|
136
135
|
@session_id = nil
|
137
136
|
result
|
@@ -140,10 +139,11 @@ end
|
|
140
139
|
|
141
140
|
# Main script
|
142
141
|
if __FILE__ == $PROGRAM_NAME
|
143
|
-
puts
|
144
|
-
|
145
|
-
|
146
|
-
|
142
|
+
puts <<~MESSAGE
|
143
|
+
MCP HTTP Client Example
|
144
|
+
Make sure the HTTP server is running (ruby examples/http_server.rb)
|
145
|
+
#{"=" * 50}
|
146
|
+
MESSAGE
|
147
147
|
|
148
148
|
client = MCPHTTPClient.new
|
149
149
|
|
data/examples/http_server.rb
CHANGED
@@ -61,8 +61,9 @@ server = MCP::Server.new(
|
|
61
61
|
prompts: [ExamplePrompt],
|
62
62
|
resources: [
|
63
63
|
MCP::Resource.new(
|
64
|
-
uri: "test_resource",
|
65
|
-
name: "
|
64
|
+
uri: "https://test_resource.invalid",
|
65
|
+
name: "test-resource",
|
66
|
+
title: "Test Resource",
|
66
67
|
description: "Test resource that echoes back the uri as its content",
|
67
68
|
mime_type: "text/plain",
|
68
69
|
),
|
@@ -153,15 +154,17 @@ rack_app = Rack::Builder.new do
|
|
153
154
|
end
|
154
155
|
|
155
156
|
# Start the server
|
156
|
-
puts
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
157
|
+
puts <<~MESSAGE
|
158
|
+
Starting MCP HTTP server on http://localhost:9292
|
159
|
+
Use POST requests to initialize and send JSON-RPC commands
|
160
|
+
Example initialization:
|
161
|
+
curl -i http://localhost:9292 --json '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'
|
162
|
+
|
163
|
+
The server will return a session ID in the Mcp-Session-Id header.
|
164
|
+
Use this session ID for subsequent requests.
|
165
|
+
|
166
|
+
Press Ctrl+C to stop the server
|
167
|
+
MESSAGE
|
165
168
|
|
166
169
|
# Run the server
|
167
170
|
# Use Rackup to run the server
|
data/examples/stdio_server.rb
CHANGED
@@ -58,8 +58,9 @@ server = MCP::Server.new(
|
|
58
58
|
prompts: [ExamplePrompt],
|
59
59
|
resources: [
|
60
60
|
MCP::Resource.new(
|
61
|
-
uri: "test_resource",
|
62
|
-
name: "
|
61
|
+
uri: "https://test_resource.invalid",
|
62
|
+
name: "test-resource",
|
63
|
+
title: "Test Resource",
|
63
64
|
description: "Test resource that echoes back the uri as its content",
|
64
65
|
mime_type: "text/plain",
|
65
66
|
),
|
@@ -85,7 +86,7 @@ server.resources_read_handler do |params|
|
|
85
86
|
[{
|
86
87
|
uri: params[:uri],
|
87
88
|
mimeType: "text/plain",
|
88
|
-
text: "Hello, world!",
|
89
|
+
text: "Hello, world! URI: #{params[:uri]}",
|
89
90
|
}]
|
90
91
|
end
|
91
92
|
|
@@ -92,7 +92,6 @@ def main
|
|
92
92
|
end
|
93
93
|
|
94
94
|
puts "=== MCP SSE Test Client ==="
|
95
|
-
puts ""
|
96
95
|
|
97
96
|
# Step 1: Initialize session
|
98
97
|
logger.info("Initializing session...")
|
@@ -134,13 +133,16 @@ def main
|
|
134
133
|
|
135
134
|
# Step 3: Interactive menu
|
136
135
|
loop do
|
137
|
-
puts
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
136
|
+
puts <<~MESSAGE.chomp
|
137
|
+
|
138
|
+
=== Available Actions ===
|
139
|
+
1. Send custom notification
|
140
|
+
2. Test echo
|
141
|
+
3. List tools
|
142
|
+
0. Exit
|
143
|
+
|
144
|
+
Choose an action:#{" "}
|
145
|
+
MESSAGE
|
144
146
|
|
145
147
|
choice = gets.chomp
|
146
148
|
|
@@ -167,19 +169,15 @@ def main
|
|
167
169
|
else
|
168
170
|
logger.error("Error: #{response[:body]["error"]}")
|
169
171
|
end
|
170
|
-
|
171
172
|
when "2"
|
172
173
|
print("Enter message to echo: ")
|
173
174
|
message = gets.chomp
|
174
175
|
make_request(session_id, "tools/call", { name: "echo", arguments: { message: message } })
|
175
|
-
|
176
176
|
when "3"
|
177
177
|
make_request(session_id, "tools/list")
|
178
|
-
|
179
178
|
when "0"
|
180
179
|
logger.info("Exiting...")
|
181
180
|
break
|
182
|
-
|
183
181
|
else
|
184
182
|
puts "Invalid choice"
|
185
183
|
end
|
@@ -136,37 +136,38 @@ rack_app = Rack::Builder.new do
|
|
136
136
|
end
|
137
137
|
|
138
138
|
# Print usage instructions
|
139
|
-
puts
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
139
|
+
puts <<~MESSAGE
|
140
|
+
=== MCP Streaming HTTP Test Server ===
|
141
|
+
|
142
|
+
Starting server on http://localhost:9393
|
143
|
+
|
144
|
+
Available Tools:
|
145
|
+
1. NotificationTool - Returns messages that are sent via SSE when stream is active"
|
146
|
+
2. echo - Simple echo tool
|
147
|
+
|
148
|
+
Testing SSE:
|
149
|
+
|
150
|
+
1. Initialize session:
|
151
|
+
curl -i http://localhost:9393 \\
|
152
|
+
--json '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"sse-test","version":"1.0"}}}'
|
153
|
+
|
154
|
+
2. Connect SSE stream (use the session ID from step 1):"
|
155
|
+
curl -i -N -H "Mcp-Session-Id: YOUR_SESSION_ID" http://localhost:9393
|
156
|
+
|
157
|
+
3. In another terminal, test tools (responses will be sent via SSE if stream is active):
|
158
|
+
|
159
|
+
Echo tool:
|
160
|
+
curl -i http://localhost:9393 -H "Mcp-Session-Id: YOUR_SESSION_ID" \\
|
161
|
+
--json '{"jsonrpc":"2.0","method":"tools/call","id":2,"params":{"name":"echo","arguments":{"message":"Hello SSE!"}}}'
|
162
|
+
|
163
|
+
Notification tool (with 2 second delay):
|
164
|
+
curl -i http://localhost:9393 -H "Mcp-Session-Id: YOUR_SESSION_ID" \\
|
165
|
+
--json '{"jsonrpc":"2.0","method":"tools/call","id":3,"params":{"name":"notification_tool","arguments":{"message":"Hello SSE!", "delay": 2}}}'
|
166
|
+
|
167
|
+
Note: When an SSE stream is active, tool responses will appear in the SSE stream and the POST request will return {"accepted": true}
|
168
|
+
|
169
|
+
Press Ctrl+C to stop the server
|
170
|
+
MESSAGE
|
170
171
|
|
171
172
|
# Start the server
|
172
173
|
Rackup::Handler.get("puma").run(rack_app, Port: 9393, Host: "localhost")
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MCP
|
4
|
+
class Client
|
5
|
+
class HTTP
|
6
|
+
attr_reader :url
|
7
|
+
|
8
|
+
def initialize(url:, headers: {})
|
9
|
+
@url = url
|
10
|
+
@headers = headers
|
11
|
+
end
|
12
|
+
|
13
|
+
def send_request(request:)
|
14
|
+
method = request[:method] || request["method"]
|
15
|
+
params = request[:params] || request["params"]
|
16
|
+
|
17
|
+
client.post("", request).body
|
18
|
+
rescue Faraday::BadRequestError => e
|
19
|
+
raise RequestHandlerError.new(
|
20
|
+
"The #{method} request is invalid",
|
21
|
+
{ method:, params: },
|
22
|
+
error_type: :bad_request,
|
23
|
+
original_error: e,
|
24
|
+
)
|
25
|
+
rescue Faraday::UnauthorizedError => e
|
26
|
+
raise RequestHandlerError.new(
|
27
|
+
"You are unauthorized to make #{method} requests",
|
28
|
+
{ method:, params: },
|
29
|
+
error_type: :unauthorized,
|
30
|
+
original_error: e,
|
31
|
+
)
|
32
|
+
rescue Faraday::ForbiddenError => e
|
33
|
+
raise RequestHandlerError.new(
|
34
|
+
"You are forbidden to make #{method} requests",
|
35
|
+
{ method:, params: },
|
36
|
+
error_type: :forbidden,
|
37
|
+
original_error: e,
|
38
|
+
)
|
39
|
+
rescue Faraday::ResourceNotFound => e
|
40
|
+
raise RequestHandlerError.new(
|
41
|
+
"The #{method} request is not found",
|
42
|
+
{ method:, params: },
|
43
|
+
error_type: :not_found,
|
44
|
+
original_error: e,
|
45
|
+
)
|
46
|
+
rescue Faraday::UnprocessableEntityError => e
|
47
|
+
raise RequestHandlerError.new(
|
48
|
+
"The #{method} request is unprocessable",
|
49
|
+
{ method:, params: },
|
50
|
+
error_type: :unprocessable_entity,
|
51
|
+
original_error: e,
|
52
|
+
)
|
53
|
+
rescue Faraday::Error => e # Catch-all
|
54
|
+
raise RequestHandlerError.new(
|
55
|
+
"Internal error handling #{method} request",
|
56
|
+
{ method:, params: },
|
57
|
+
error_type: :internal_error,
|
58
|
+
original_error: e,
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
attr_reader :headers
|
65
|
+
|
66
|
+
def client
|
67
|
+
require_faraday!
|
68
|
+
@client ||= Faraday.new(url) do |faraday|
|
69
|
+
faraday.request(:json)
|
70
|
+
faraday.response(:json)
|
71
|
+
faraday.response(:raise_error)
|
72
|
+
|
73
|
+
headers.each do |key, value|
|
74
|
+
faraday.headers[key] = value
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def require_faraday!
|
80
|
+
require "faraday"
|
81
|
+
rescue LoadError
|
82
|
+
raise LoadError, "The 'faraday' gem is required to use the MCP client HTTP transport. " \
|
83
|
+
"Add it to your Gemfile: gem 'faraday', '>= 2.0'" \
|
84
|
+
"See https://rubygems.org/gems/faraday for more details."
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MCP
|
4
|
+
class Client
|
5
|
+
class Tool
|
6
|
+
attr_reader :name, :description, :input_schema, :output_schema
|
7
|
+
|
8
|
+
def initialize(name:, description:, input_schema:, output_schema: nil)
|
9
|
+
@name = name
|
10
|
+
@description = description
|
11
|
+
@input_schema = input_schema
|
12
|
+
@output_schema = output_schema
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/mcp/client.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MCP
|
4
|
+
class Client
|
5
|
+
# Initializes a new MCP::Client instance.
|
6
|
+
#
|
7
|
+
# @param transport [Object] The transport object to use for communication with the server.
|
8
|
+
# The transport should be a duck type that responds to `send_request`. See the README for more details.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# transport = MCP::Client::HTTP.new(url: "http://localhost:3000")
|
12
|
+
# client = MCP::Client.new(transport: transport)
|
13
|
+
def initialize(transport:)
|
14
|
+
@transport = transport
|
15
|
+
end
|
16
|
+
|
17
|
+
# The user may want to access additional transport-specific methods/attributes
|
18
|
+
# So keeping it public
|
19
|
+
attr_reader :transport
|
20
|
+
|
21
|
+
# Returns the list of tools available from the server.
|
22
|
+
# Each call will make a new request – the result is not cached.
|
23
|
+
#
|
24
|
+
# @return [Array<MCP::Client::Tool>] An array of available tools.
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# tools = client.tools
|
28
|
+
# tools.each do |tool|
|
29
|
+
# puts tool.name
|
30
|
+
# end
|
31
|
+
def tools
|
32
|
+
response = transport.send_request(request: {
|
33
|
+
jsonrpc: JsonRpcHandler::Version::V2_0,
|
34
|
+
id: request_id,
|
35
|
+
method: "tools/list",
|
36
|
+
})
|
37
|
+
|
38
|
+
response.dig("result", "tools")&.map do |tool|
|
39
|
+
Tool.new(
|
40
|
+
name: tool["name"],
|
41
|
+
description: tool["description"],
|
42
|
+
input_schema: tool["inputSchema"],
|
43
|
+
)
|
44
|
+
end || []
|
45
|
+
end
|
46
|
+
|
47
|
+
# Calls a tool via the transport layer.
|
48
|
+
#
|
49
|
+
# @param tool [MCP::Client::Tool] The tool to be called.
|
50
|
+
# @param arguments [Object, nil] The arguments to pass to the tool.
|
51
|
+
# @return [Object] The result of the tool call, as returned by the transport.
|
52
|
+
#
|
53
|
+
# @example
|
54
|
+
# tool = client.tools.first
|
55
|
+
# result = client.call_tool(tool: tool, arguments: { foo: "bar" })
|
56
|
+
#
|
57
|
+
# @note
|
58
|
+
# The exact requirements for `arguments` are determined by the transport layer in use.
|
59
|
+
# Consult the documentation for your transport (e.g., MCP::Client::HTTP) for details.
|
60
|
+
def call_tool(tool:, arguments: nil)
|
61
|
+
response = transport.send_request(request: {
|
62
|
+
jsonrpc: JsonRpcHandler::Version::V2_0,
|
63
|
+
id: request_id,
|
64
|
+
method: "tools/call",
|
65
|
+
params: { name: tool.name, arguments: arguments },
|
66
|
+
})
|
67
|
+
|
68
|
+
response.dig("result", "content")
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def request_id
|
74
|
+
SecureRandom.uuid
|
75
|
+
end
|
76
|
+
|
77
|
+
class RequestHandlerError < StandardError
|
78
|
+
attr_reader :error_type, :original_error, :request
|
79
|
+
|
80
|
+
def initialize(message, request, error_type: :internal_error, original_error: nil)
|
81
|
+
super(message)
|
82
|
+
@request = request
|
83
|
+
@error_type = error_type
|
84
|
+
@original_error = original_error
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/mcp/configuration.rb
CHANGED
@@ -2,7 +2,8 @@
|
|
2
2
|
|
3
3
|
module MCP
|
4
4
|
class Configuration
|
5
|
-
DEFAULT_PROTOCOL_VERSION = "
|
5
|
+
DEFAULT_PROTOCOL_VERSION = "2025-06-18"
|
6
|
+
SUPPORTED_PROTOCOL_VERSIONS = [DEFAULT_PROTOCOL_VERSION, "2025-03-26", "2024-11-05"]
|
6
7
|
|
7
8
|
attr_writer :exception_reporter, :instrumentation_callback, :protocol_version, :validate_tool_call_arguments
|
8
9
|
|
@@ -11,6 +12,10 @@ module MCP
|
|
11
12
|
@exception_reporter = exception_reporter
|
12
13
|
@instrumentation_callback = instrumentation_callback
|
13
14
|
@protocol_version = protocol_version
|
15
|
+
if protocol_version && !SUPPORTED_PROTOCOL_VERSIONS.include?(protocol_version)
|
16
|
+
message = "protocol_version must be #{SUPPORTED_PROTOCOL_VERSIONS[0...-1].join(", ")}, or #{SUPPORTED_PROTOCOL_VERSIONS[-1]}"
|
17
|
+
raise ArgumentError, message
|
18
|
+
end
|
14
19
|
unless validate_tool_call_arguments.is_a?(TrueClass) || validate_tool_call_arguments.is_a?(FalseClass)
|
15
20
|
raise ArgumentError, "validate_tool_call_arguments must be a boolean"
|
16
21
|
end
|
data/lib/mcp/prompt.rb
CHANGED
@@ -6,6 +6,7 @@ module MCP
|
|
6
6
|
class << self
|
7
7
|
NOT_SET = Object.new
|
8
8
|
|
9
|
+
attr_reader :title_value
|
9
10
|
attr_reader :description_value
|
10
11
|
attr_reader :arguments_value
|
11
12
|
|
@@ -14,12 +15,13 @@ module MCP
|
|
14
15
|
end
|
15
16
|
|
16
17
|
def to_h
|
17
|
-
{ name: name_value, description: description_value, arguments: arguments_value.map(&:to_h) }.compact
|
18
|
+
{ name: name_value, title: title_value, description: description_value, arguments: arguments_value.map(&:to_h) }.compact
|
18
19
|
end
|
19
20
|
|
20
21
|
def inherited(subclass)
|
21
22
|
super
|
22
23
|
subclass.instance_variable_set(:@name_value, nil)
|
24
|
+
subclass.instance_variable_set(:@title_value, nil)
|
23
25
|
subclass.instance_variable_set(:@description_value, nil)
|
24
26
|
subclass.instance_variable_set(:@arguments_value, nil)
|
25
27
|
end
|
@@ -36,6 +38,14 @@ module MCP
|
|
36
38
|
@name_value || StringUtils.handle_from_class_name(name)
|
37
39
|
end
|
38
40
|
|
41
|
+
def title(value = NOT_SET)
|
42
|
+
if value == NOT_SET
|
43
|
+
@title_value
|
44
|
+
else
|
45
|
+
@title_value = value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
39
49
|
def description(value = NOT_SET)
|
40
50
|
if value == NOT_SET
|
41
51
|
@description_value
|
@@ -52,9 +62,10 @@ module MCP
|
|
52
62
|
end
|
53
63
|
end
|
54
64
|
|
55
|
-
def define(name: nil, description: nil, arguments: [], &block)
|
65
|
+
def define(name: nil, title: nil, description: nil, arguments: [], &block)
|
56
66
|
Class.new(self) do
|
57
67
|
prompt_name name
|
68
|
+
title title
|
58
69
|
description description
|
59
70
|
arguments arguments
|
60
71
|
define_singleton_method(:template) do |args, server_context: nil|
|
data/lib/mcp/resource.rb
CHANGED
@@ -3,21 +3,23 @@
|
|
3
3
|
|
4
4
|
module MCP
|
5
5
|
class Resource
|
6
|
-
attr_reader :uri, :name, :description, :mime_type
|
6
|
+
attr_reader :uri, :name, :title, :description, :mime_type
|
7
7
|
|
8
|
-
def initialize(uri:, name:, description: nil, mime_type: nil)
|
8
|
+
def initialize(uri:, name:, title: nil, description: nil, mime_type: nil)
|
9
9
|
@uri = uri
|
10
10
|
@name = name
|
11
|
+
@title = title
|
11
12
|
@description = description
|
12
13
|
@mime_type = mime_type
|
13
14
|
end
|
14
15
|
|
15
16
|
def to_h
|
16
17
|
{
|
17
|
-
uri:
|
18
|
-
name:
|
19
|
-
|
20
|
-
|
18
|
+
uri: uri,
|
19
|
+
name: name,
|
20
|
+
title: title,
|
21
|
+
description: description,
|
22
|
+
mimeType: mime_type,
|
21
23
|
}.compact
|
22
24
|
end
|
23
25
|
end
|
@@ -3,21 +3,23 @@
|
|
3
3
|
|
4
4
|
module MCP
|
5
5
|
class ResourceTemplate
|
6
|
-
attr_reader :uri_template, :name, :description, :mime_type
|
6
|
+
attr_reader :uri_template, :name, :title, :description, :mime_type
|
7
7
|
|
8
|
-
def initialize(uri_template:, name:, description: nil, mime_type: nil)
|
8
|
+
def initialize(uri_template:, name:, title: nil, description: nil, mime_type: nil)
|
9
9
|
@uri_template = uri_template
|
10
10
|
@name = name
|
11
|
+
@title = title
|
11
12
|
@description = description
|
12
13
|
@mime_type = mime_type
|
13
14
|
end
|
14
15
|
|
15
16
|
def to_h
|
16
17
|
{
|
17
|
-
uriTemplate:
|
18
|
-
name:
|
19
|
-
|
20
|
-
|
18
|
+
uriTemplate: uri_template,
|
19
|
+
name: name,
|
20
|
+
title: title,
|
21
|
+
description: description,
|
22
|
+
mimeType: mime_type,
|
21
23
|
}.compact
|
22
24
|
end
|
23
25
|
end
|
@@ -34,7 +34,13 @@ module MCP
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
def send_notification(
|
37
|
+
def send_notification(method, params = nil, session_id: nil)
|
38
|
+
notification = {
|
39
|
+
jsonrpc: "2.0",
|
40
|
+
method:,
|
41
|
+
}
|
42
|
+
notification[:params] = params if params
|
43
|
+
|
38
44
|
@mutex.synchronize do
|
39
45
|
if session_id
|
40
46
|
# Send to specific session
|
@@ -102,6 +108,8 @@ module MCP
|
|
102
108
|
|
103
109
|
if body["method"] == "initialize"
|
104
110
|
handle_initialization(body_string, body)
|
111
|
+
elsif body["method"] == MCP::Methods::NOTIFICATIONS_INITIALIZED
|
112
|
+
handle_notification_initialized
|
105
113
|
else
|
106
114
|
handle_regular_request(body_string, session_id)
|
107
115
|
end
|
@@ -179,6 +187,10 @@ module MCP
|
|
179
187
|
[200, headers, [response]]
|
180
188
|
end
|
181
189
|
|
190
|
+
def handle_notification_initialized
|
191
|
+
[202, {}, []]
|
192
|
+
end
|
193
|
+
|
182
194
|
def handle_regular_request(body_string, session_id)
|
183
195
|
# If session ID is provided, but not in the sessions hash, return an error
|
184
196
|
if session_id && !@sessions.key?(session_id)
|