model-context-protocol-rb 0.5.1 → 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 +4 -4
- data/CHANGELOG.md +37 -1
- data/README.md +181 -950
- data/lib/model_context_protocol/rspec/helpers.rb +54 -0
- data/lib/model_context_protocol/rspec/matchers/be_mcp_error_response.rb +123 -0
- data/lib/model_context_protocol/rspec/matchers/be_valid_mcp_class.rb +103 -0
- data/lib/model_context_protocol/rspec/matchers/be_valid_mcp_prompt_response.rb +126 -0
- data/lib/model_context_protocol/rspec/matchers/be_valid_mcp_resource_response.rb +121 -0
- data/lib/model_context_protocol/rspec/matchers/be_valid_mcp_tool_response.rb +135 -0
- data/lib/model_context_protocol/rspec/matchers/have_audio_content.rb +109 -0
- data/lib/model_context_protocol/rspec/matchers/have_embedded_resource_content.rb +150 -0
- data/lib/model_context_protocol/rspec/matchers/have_image_content.rb +109 -0
- data/lib/model_context_protocol/rspec/matchers/have_message_count.rb +87 -0
- data/lib/model_context_protocol/rspec/matchers/have_message_with_role.rb +152 -0
- data/lib/model_context_protocol/rspec/matchers/have_resource_annotations.rb +135 -0
- data/lib/model_context_protocol/rspec/matchers/have_resource_blob.rb +108 -0
- data/lib/model_context_protocol/rspec/matchers/have_resource_link_content.rb +138 -0
- data/lib/model_context_protocol/rspec/matchers/have_resource_mime_type.rb +103 -0
- data/lib/model_context_protocol/rspec/matchers/have_resource_text.rb +112 -0
- data/lib/model_context_protocol/rspec/matchers/have_structured_content.rb +88 -0
- data/lib/model_context_protocol/rspec/matchers/have_text_content.rb +113 -0
- data/lib/model_context_protocol/rspec/matchers.rb +31 -0
- data/lib/model_context_protocol/rspec.rb +23 -0
- data/lib/model_context_protocol/server/cancellable.rb +5 -5
- data/lib/model_context_protocol/server/{mcp_logger.rb → client_logger.rb} +8 -11
- data/lib/model_context_protocol/server/configuration.rb +196 -109
- data/lib/model_context_protocol/server/content_helpers.rb +1 -1
- data/lib/model_context_protocol/server/global_config/server_logging.rb +78 -0
- data/lib/model_context_protocol/server/progressable.rb +43 -21
- data/lib/model_context_protocol/server/prompt.rb +12 -21
- data/lib/model_context_protocol/server/redis_client_proxy.rb +2 -14
- data/lib/model_context_protocol/server/redis_config.rb +5 -7
- data/lib/model_context_protocol/server/redis_pool_manager.rb +11 -14
- data/lib/model_context_protocol/server/registry.rb +8 -0
- data/lib/model_context_protocol/server/resource.rb +7 -4
- data/lib/model_context_protocol/server/router.rb +285 -9
- data/lib/model_context_protocol/server/server_logger.rb +31 -0
- data/lib/model_context_protocol/server/stdio_configuration.rb +114 -0
- data/lib/model_context_protocol/server/stdio_transport/request_store.rb +12 -53
- data/lib/model_context_protocol/server/stdio_transport.rb +18 -12
- data/lib/model_context_protocol/server/streamable_http_configuration.rb +218 -0
- data/lib/model_context_protocol/server/streamable_http_transport/event_counter.rb +0 -13
- data/lib/model_context_protocol/server/streamable_http_transport/message_poller.rb +9 -9
- data/lib/model_context_protocol/server/streamable_http_transport/notification_queue.rb +0 -41
- data/lib/model_context_protocol/server/streamable_http_transport/request_store.rb +21 -124
- data/lib/model_context_protocol/server/streamable_http_transport/server_request_store.rb +167 -0
- data/lib/model_context_protocol/server/streamable_http_transport/session_message_queue.rb +0 -58
- data/lib/model_context_protocol/server/streamable_http_transport/session_store.rb +17 -31
- data/lib/model_context_protocol/server/streamable_http_transport/stream_registry.rb +0 -34
- data/lib/model_context_protocol/server/streamable_http_transport.rb +589 -215
- data/lib/model_context_protocol/server/tool.rb +73 -6
- data/lib/model_context_protocol/server.rb +204 -261
- data/lib/model_context_protocol/version.rb +1 -1
- data/lib/model_context_protocol.rb +4 -1
- data/lib/puma/plugin/mcp.rb +39 -0
- data/tasks/mcp.rake +26 -0
- data/tasks/templates/dev-http-puma.erb +251 -0
- data/tasks/templates/dev-http.erb +166 -184
- data/tasks/templates/dev.erb +29 -7
- metadata +33 -6
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env <%= @ruby_path %>
|
|
2
2
|
|
|
3
3
|
require "bundler/setup"
|
|
4
|
+
require "fileutils"
|
|
4
5
|
require "rack"
|
|
5
6
|
require 'rackup/handler/webrick'
|
|
6
7
|
require "webrick"
|
|
@@ -10,31 +11,94 @@ require "securerandom"
|
|
|
10
11
|
require "redis"
|
|
11
12
|
require "logger"
|
|
12
13
|
require "json"
|
|
13
|
-
require
|
|
14
|
+
require "stringio"
|
|
14
15
|
|
|
15
16
|
require_relative "../lib/model_context_protocol"
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
config.enable_reaper = true
|
|
21
|
-
config.reaper_interval = 10
|
|
22
|
-
config.idle_timeout = 15
|
|
18
|
+
# Only require test handler files (prompts, resources, tools, etc.), not RSpec helpers
|
|
19
|
+
%w[prompts resources resource_templates tools completions].each do |subdir|
|
|
20
|
+
Dir[File.join(__dir__, "../spec/support/#{subdir}/**/*.rb")].each { |file| require file }
|
|
23
21
|
end
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
# Flag files for dynamic handler registration (checked at startup)
|
|
24
|
+
FLAGS_DIR = File.join(__dir__, '..', 'tmp', 'flags')
|
|
25
|
+
FileUtils.mkdir_p(FLAGS_DIR) unless Dir.exist?(FLAGS_DIR)
|
|
26
|
+
|
|
27
|
+
def flag_enabled?(flag_name)
|
|
28
|
+
File.exist?(File.join(FLAGS_DIR, flag_name))
|
|
29
|
+
end
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
# Configure server logging
|
|
32
|
+
ModelContextProtocol::Server.configure_server_logging do |config|
|
|
33
|
+
config.logdev = $stdout
|
|
34
|
+
config.level = Logger::DEBUG
|
|
35
|
+
config.progname = "MCP-Dev-Server"
|
|
32
36
|
end
|
|
33
37
|
|
|
38
|
+
# Configure MCP server
|
|
39
|
+
ModelContextProtocol::Server.with_streamable_http_transport do |config|
|
|
40
|
+
config.name = "MCP Development Server"
|
|
41
|
+
config.version = "1.0.0"
|
|
42
|
+
|
|
43
|
+
config.redis_url = "redis://localhost:6379/0"
|
|
44
|
+
config.redis_pool_size = 10
|
|
45
|
+
config.redis_enable_reaper = true
|
|
46
|
+
config.redis_reaper_interval = 10
|
|
47
|
+
config.redis_idle_timeout = 15
|
|
48
|
+
|
|
49
|
+
config.pagination = {
|
|
50
|
+
default_page_size: 2,
|
|
51
|
+
max_page_size: 3,
|
|
52
|
+
cursor_ttl: 1800
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
config.require_sessions = true
|
|
56
|
+
config.session_ttl = 3600
|
|
57
|
+
config.allowed_origins = ["*"]
|
|
58
|
+
|
|
59
|
+
config.registry do
|
|
60
|
+
prompts do
|
|
61
|
+
register TestPrompt
|
|
62
|
+
register TestPromptWithCompletionClass if flag_enabled?('extra_prompts')
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
resources subscribe: true do
|
|
66
|
+
register TestResource
|
|
67
|
+
register TestAnnotatedResource
|
|
68
|
+
register TestProgressiveResource
|
|
69
|
+
register TestBinaryResource if flag_enabled?('extra_resources')
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
resource_templates do
|
|
73
|
+
register TestResourceTemplate
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
tools do
|
|
77
|
+
register TestToolWithStructuredContentResponse
|
|
78
|
+
register TestToolWithTextResponse
|
|
79
|
+
register TestToolWithImageResponse
|
|
80
|
+
register TestToolWithMixedContentResponse
|
|
81
|
+
register TestToolWithResourceResponse
|
|
82
|
+
register TestToolWithToolErrorResponse
|
|
83
|
+
register TestToolWithCancellableSleep
|
|
84
|
+
register TestToolWithProgressableAndCancellable
|
|
85
|
+
register TestToolWithAnnotations
|
|
86
|
+
register TestToolWithSecuritySchemes
|
|
87
|
+
register TestToolWithResourceLinkResponse
|
|
88
|
+
register TestToolWithAudioResponse if flag_enabled?('extra_tools')
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
34
92
|
|
|
93
|
+
# Rack application
|
|
35
94
|
class MCPHttpApp
|
|
36
|
-
def initialize
|
|
37
|
-
@logger =
|
|
95
|
+
def initialize
|
|
96
|
+
@logger = Logger.new(STDOUT)
|
|
97
|
+
@logger.level = Logger::INFO
|
|
98
|
+
@logger.formatter = proc do |severity, datetime, progname, msg|
|
|
99
|
+
request_id = Thread.current[:request_id] || "----"
|
|
100
|
+
"[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{severity} [#{request_id}]: #{msg}\n"
|
|
101
|
+
end
|
|
38
102
|
end
|
|
39
103
|
|
|
40
104
|
def call(env)
|
|
@@ -44,6 +108,40 @@ class MCPHttpApp
|
|
|
44
108
|
request = Rack::Request.new(env)
|
|
45
109
|
body_content = request.body.read
|
|
46
110
|
|
|
111
|
+
log_request(env, body_content)
|
|
112
|
+
|
|
113
|
+
env['rack.input'] = StringIO.new(body_content)
|
|
114
|
+
|
|
115
|
+
unless request.path == "/mcp"
|
|
116
|
+
return [404, {"Content-Type" => "application/json"}, ['{"error": "Not found"}']]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
if request.request_method == "OPTIONS"
|
|
120
|
+
return [200, cors_headers, [""]]
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
begin
|
|
124
|
+
result = ModelContextProtocol::Server.serve(
|
|
125
|
+
env: env,
|
|
126
|
+
session_context: {
|
|
127
|
+
user_id: "dev-user-123",
|
|
128
|
+
request_id: request_id
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
handle_result(result, body_content)
|
|
133
|
+
rescue => e
|
|
134
|
+
@logger.error("Error handling request: #{e.message}")
|
|
135
|
+
@logger.debug("Full backtrace:\n#{e.backtrace.join("\n")}")
|
|
136
|
+
[500, {"Content-Type" => "application/json"}, [%Q({"error": "Internal server error: #{e.message}"})]]
|
|
137
|
+
ensure
|
|
138
|
+
Thread.current[:request_id] = nil
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
private
|
|
143
|
+
|
|
144
|
+
def log_request(env, body_content)
|
|
47
145
|
case env['REQUEST_METHOD']
|
|
48
146
|
when 'POST'
|
|
49
147
|
begin
|
|
@@ -51,9 +149,7 @@ class MCPHttpApp
|
|
|
51
149
|
method = request_json['method']
|
|
52
150
|
id = request_json['id']
|
|
53
151
|
|
|
54
|
-
if method&.start_with?('notifications/')
|
|
55
|
-
@logger.info("→ #{method} [NOTIFICATION]")
|
|
56
|
-
elsif id.nil?
|
|
152
|
+
if method&.start_with?('notifications/') || id.nil?
|
|
57
153
|
@logger.info("→ #{method} [NOTIFICATION]")
|
|
58
154
|
else
|
|
59
155
|
@logger.info("→ #{method} (id: #{id}) [REQUEST]")
|
|
@@ -61,7 +157,6 @@ class MCPHttpApp
|
|
|
61
157
|
@logger.info(" Request: #{body_content}")
|
|
62
158
|
rescue JSON::ParserError
|
|
63
159
|
@logger.info("→ POST #{env['PATH_INFO']} [INVALID JSON]")
|
|
64
|
-
@logger.info(" Request: #{body_content}")
|
|
65
160
|
end
|
|
66
161
|
when 'GET'
|
|
67
162
|
accept_header = env['HTTP_ACCEPT'] || ''
|
|
@@ -70,173 +165,45 @@ class MCPHttpApp
|
|
|
70
165
|
else
|
|
71
166
|
@logger.info("→ GET #{env['PATH_INFO']}")
|
|
72
167
|
end
|
|
73
|
-
@logger.info(" Headers: Accept=#{accept_header}") unless accept_header.empty?
|
|
74
168
|
when 'DELETE'
|
|
75
169
|
session_id = env['HTTP_MCP_SESSION_ID']
|
|
76
|
-
|
|
77
|
-
@logger.info("→ DELETE #{env['PATH_INFO']} [SESSION CLEANUP: #{session_id}]")
|
|
78
|
-
else
|
|
79
|
-
@logger.info("→ DELETE #{env['PATH_INFO']} [SESSION CLEANUP]")
|
|
80
|
-
end
|
|
170
|
+
@logger.info("→ DELETE #{env['PATH_INFO']} [SESSION CLEANUP#{session_id ? ": #{session_id}" : ""}]")
|
|
81
171
|
else
|
|
82
172
|
@logger.info("→ #{env['REQUEST_METHOD']} #{env['PATH_INFO']}")
|
|
83
|
-
@logger.info(" Request: #{body_content}") unless body_content.empty?
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
if ModelContextProtocol::Server::RedisConfig.configured?
|
|
87
|
-
pool_stats = ModelContextProtocol::Server::RedisConfig.stats
|
|
88
|
-
@logger.info(" Redis Pool: #{pool_stats}")
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
env['rack.input'] = StringIO.new(body_content)
|
|
92
|
-
request = Rack::Request.new(env)
|
|
93
|
-
|
|
94
|
-
unless request.path == "/mcp"
|
|
95
|
-
return [404, {"Content-Type" => "application/json"}, ['{"error": "Not found"}']]
|
|
96
173
|
end
|
|
174
|
+
end
|
|
97
175
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
"
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
allowed_origins: ["*"]
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
@logger.debug("Creating MCP server with transport config")
|
|
116
|
-
server = create_mcp_server(transport_config)
|
|
117
|
-
transport = nil
|
|
118
|
-
|
|
119
|
-
begin
|
|
120
|
-
@logger.debug("Starting MCP server")
|
|
121
|
-
result = server.start
|
|
122
|
-
|
|
123
|
-
if server.respond_to?(:transport)
|
|
124
|
-
transport = server.transport
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
case result
|
|
128
|
-
when Hash
|
|
129
|
-
if result[:stream]
|
|
130
|
-
@logger.info("← SSE STREAM OPENED [PERSISTENT CONNECTION]")
|
|
131
|
-
@logger.info(" Connection will remain open for real-time notifications")
|
|
132
|
-
headers = result[:headers] || {}
|
|
133
|
-
headers["Access-Control-Allow-Origin"] = "*"
|
|
134
|
-
headers["Access-Control-Allow-Methods"] = "GET, POST, DELETE, OPTIONS"
|
|
135
|
-
headers["Access-Control-Allow-Headers"] = "Content-Type, Accept, Mcp-Session-Id, MCP-Protocol-Version, Origin"
|
|
136
|
-
|
|
137
|
-
return [200, headers, result[:stream_proc]]
|
|
138
|
-
elsif result[:json]
|
|
139
|
-
response_body = result[:json].to_json
|
|
140
|
-
status = result[:status] || 200
|
|
141
|
-
|
|
142
|
-
begin
|
|
143
|
-
response_json = result[:json]
|
|
144
|
-
if response_json[:error]
|
|
145
|
-
@logger.info("← ERROR RESPONSE (code: #{response_json[:error][:code]})")
|
|
146
|
-
elsif status == 202
|
|
147
|
-
@logger.info("← NOTIFICATION ACCEPTED [NO RESPONSE REQUIRED]")
|
|
148
|
-
elsif response_json[:accepted] == true && status == 200
|
|
149
|
-
method = request_json['method'] rescue 'unknown'
|
|
150
|
-
id = request_json['id'] rescue 'unknown'
|
|
151
|
-
@logger.info("← #{method} RESPONSE (id: #{id}) [DELIVERED VIA SSE STREAM]")
|
|
152
|
-
elsif response_json[:result]
|
|
153
|
-
method = request_json['method'] rescue 'unknown'
|
|
154
|
-
@logger.info("← #{method} RESPONSE (id: #{response_json[:id]})")
|
|
155
|
-
else
|
|
156
|
-
@logger.info("← RESPONSE (status: #{status})")
|
|
157
|
-
end
|
|
158
|
-
@logger.info(" Response: #{response_body}") unless status == 202 && response_body == '{}'
|
|
159
|
-
rescue
|
|
160
|
-
@logger.info("← RESPONSE (status: #{status})")
|
|
161
|
-
@logger.info(" Response: #{response_body}") unless response_body.empty?
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
headers = result[:headers] || {}
|
|
165
|
-
headers["Content-Type"] = "application/json"
|
|
166
|
-
headers["Access-Control-Allow-Origin"] = "*"
|
|
167
|
-
headers["Access-Control-Allow-Methods"] = "GET, POST, DELETE, OPTIONS"
|
|
168
|
-
headers["Access-Control-Allow-Headers"] = "Content-Type, Accept, Mcp-Session-Id, MCP-Protocol-Version, Origin"
|
|
169
|
-
|
|
170
|
-
[result[:status] || 200, headers, [response_body]]
|
|
171
|
-
else
|
|
172
|
-
# Fallback
|
|
173
|
-
@logger.error("← Invalid transport response")
|
|
174
|
-
[500, {"Content-Type" => "application/json"}, ['{"error": "Invalid transport response"}']]
|
|
175
|
-
end
|
|
176
|
+
def handle_result(result, body_content)
|
|
177
|
+
case result
|
|
178
|
+
when Hash
|
|
179
|
+
if result[:stream]
|
|
180
|
+
@logger.info("← SSE STREAM OPENED [PERSISTENT CONNECTION]")
|
|
181
|
+
headers = (result[:headers] || {}).merge(cors_headers)
|
|
182
|
+
[200, headers, result[:stream_proc]]
|
|
183
|
+
elsif result[:json]
|
|
184
|
+
response_body = result[:json].to_json
|
|
185
|
+
status = result[:status] || 200
|
|
186
|
+
@logger.info("← RESPONSE (status: #{status})")
|
|
187
|
+
@logger.info(" Response: #{response_body}") unless status == 202
|
|
188
|
+
headers = (result[:headers] || {}).merge(cors_headers).merge("Content-Type" => "application/json")
|
|
189
|
+
[status, headers, [response_body]]
|
|
176
190
|
else
|
|
177
|
-
@logger.error("←
|
|
178
|
-
[500, {"Content-Type" => "application/json"}, ['{"error": "
|
|
191
|
+
@logger.error("← Invalid transport response")
|
|
192
|
+
[500, {"Content-Type" => "application/json"}, ['{"error": "Invalid transport response"}']]
|
|
179
193
|
end
|
|
180
|
-
|
|
181
|
-
@logger.error("
|
|
182
|
-
|
|
183
|
-
[500, {"Content-Type" => "application/json"}, [%Q({"error": "Internal server error: #{e.message}"})]]
|
|
184
|
-
ensure
|
|
185
|
-
transport&.cleanup if transport&.respond_to?(:cleanup)
|
|
186
|
-
Thread.current[:request_id] = nil
|
|
194
|
+
else
|
|
195
|
+
@logger.error("← Unexpected response format")
|
|
196
|
+
[500, {"Content-Type" => "application/json"}, ['{"error": "Unexpected response format"}']]
|
|
187
197
|
end
|
|
188
198
|
end
|
|
189
199
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
config.pagination = {
|
|
199
|
-
default_page_size: 2,
|
|
200
|
-
max_page_size: 3,
|
|
201
|
-
cursor_ttl: 1800
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
config.set_environment_variable("MCP_ENV", "development")
|
|
205
|
-
|
|
206
|
-
config.context = {
|
|
207
|
-
user_id: "123456",
|
|
208
|
-
request_id: Thread.current[:request_id]
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
config.transport = transport_config
|
|
212
|
-
|
|
213
|
-
config.registry = ModelContextProtocol::Server::Registry.new do
|
|
214
|
-
prompts list_changed: true do
|
|
215
|
-
register TestPrompt
|
|
216
|
-
register TestPromptWithCompletionClass
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
resources list_changed: true, subscribe: true do
|
|
220
|
-
register TestResource
|
|
221
|
-
register TestAnnotatedResource
|
|
222
|
-
register TestBinaryResource
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
resource_templates do
|
|
226
|
-
register TestResourceTemplate
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
tools list_changed: true do
|
|
230
|
-
register TestToolWithStructuredContentResponse
|
|
231
|
-
register TestToolWithTextResponse
|
|
232
|
-
register TestToolWithImageResponse
|
|
233
|
-
register TestToolWithMixedContentResponse
|
|
234
|
-
register TestToolWithResourceResponse
|
|
235
|
-
register TestToolWithToolErrorResponse
|
|
236
|
-
register TestToolWithCancellableSleep
|
|
237
|
-
end
|
|
238
|
-
end
|
|
239
|
-
end
|
|
200
|
+
def cors_headers
|
|
201
|
+
{
|
|
202
|
+
"Access-Control-Allow-Origin" => "*",
|
|
203
|
+
"Access-Control-Allow-Methods" => "GET, POST, DELETE, OPTIONS",
|
|
204
|
+
"Access-Control-Allow-Headers" => "Content-Type, Accept, Mcp-Session-Id, MCP-Protocol-Version, Origin",
|
|
205
|
+
"Access-Control-Max-Age" => "86400"
|
|
206
|
+
}
|
|
240
207
|
end
|
|
241
208
|
end
|
|
242
209
|
|
|
@@ -244,11 +211,23 @@ use_ssl = ENV['SSL'] == 'true'
|
|
|
244
211
|
port = use_ssl ? 9293 : 9292
|
|
245
212
|
protocol = use_ssl ? 'https' : 'http'
|
|
246
213
|
|
|
247
|
-
|
|
214
|
+
puts "=" * 60
|
|
215
|
+
puts "MCP Development Server (WEBrick)"
|
|
216
|
+
puts "=" * 60
|
|
217
|
+
puts "URL: #{protocol}://localhost:#{port}/mcp"
|
|
218
|
+
puts "Transport: Streamable HTTP"
|
|
219
|
+
puts "=" * 60
|
|
220
|
+
puts ""
|
|
221
|
+
puts "Flag files (checked at startup only - restart server to apply changes):"
|
|
222
|
+
puts " touch tmp/flags/extra_tools # Add TestToolWithAudioResponse"
|
|
223
|
+
puts " touch tmp/flags/extra_resources # Add TestBinaryResource"
|
|
224
|
+
puts " touch tmp/flags/extra_prompts # Add TestPromptWithCompletionClass"
|
|
225
|
+
puts " rm tmp/flags/<flag> # Remove the handler"
|
|
226
|
+
puts ""
|
|
248
227
|
|
|
249
228
|
app = Rack::Builder.new do
|
|
250
229
|
map '/mcp' do
|
|
251
|
-
run MCPHttpApp.new
|
|
230
|
+
run MCPHttpApp.new
|
|
252
231
|
end
|
|
253
232
|
end
|
|
254
233
|
|
|
@@ -262,8 +241,8 @@ if use_ssl
|
|
|
262
241
|
key_path = File.join(__dir__, '..', 'tmp', 'ssl', 'server.key')
|
|
263
242
|
|
|
264
243
|
unless File.exist?(cert_path) && File.exist?(key_path)
|
|
265
|
-
|
|
266
|
-
|
|
244
|
+
$stderr.puts "SSL certificates not found at tmp/ssl/"
|
|
245
|
+
$stderr.puts "Generate them with: openssl req -x509 -newkey rsa:4096 -keyout tmp/ssl/server.key -out tmp/ssl/server.crt -days 365 -nodes -subj \"/C=US/ST=Dev/L=Dev/O=Dev/CN=localhost\""
|
|
267
246
|
exit(1)
|
|
268
247
|
end
|
|
269
248
|
|
|
@@ -275,14 +254,17 @@ if use_ssl
|
|
|
275
254
|
)
|
|
276
255
|
end
|
|
277
256
|
|
|
278
|
-
server
|
|
279
|
-
|
|
257
|
+
# Start the MCP server (lifecycle hook)
|
|
258
|
+
ModelContextProtocol::Server.start
|
|
259
|
+
|
|
260
|
+
webrick_server = WEBrick::HTTPServer.new(server_options)
|
|
261
|
+
webrick_server.mount '/', Rackup::Handler::WEBrick, app
|
|
280
262
|
|
|
281
263
|
['INT', 'TERM'].each do |signal|
|
|
282
264
|
Signal.trap(signal) do
|
|
283
|
-
|
|
284
|
-
|
|
265
|
+
ModelContextProtocol::Server.shutdown
|
|
266
|
+
webrick_server.shutdown
|
|
285
267
|
end
|
|
286
268
|
end
|
|
287
269
|
|
|
288
|
-
|
|
270
|
+
webrick_server.start
|
data/tasks/templates/dev.erb
CHANGED
|
@@ -4,16 +4,30 @@ require "bundler/setup"
|
|
|
4
4
|
require "securerandom"
|
|
5
5
|
require_relative "../lib/model_context_protocol"
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
# Only require test handler files (prompts, resources, tools, etc.), not RSpec helpers
|
|
8
|
+
%w[prompts resources resource_templates tools completions].each do |subdir|
|
|
9
|
+
Dir[File.join(__dir__, "../spec/support/#{subdir}/**/*.rb")].each { |file| require file }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Configure server logging globally (once per application)
|
|
13
|
+
ModelContextProtocol::Server.configure_server_logging do |logger|
|
|
14
|
+
logger.level = Logger::DEBUG
|
|
15
|
+
logger.progname = "MCP-Dev-Server"
|
|
16
|
+
# logger.logdev = $stderr # Default for stdio transport
|
|
17
|
+
# logger.logdev = File.open("/tmp/mcp_server.log", "a") # Alternative: log to file
|
|
18
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
|
19
|
+
timestamp = datetime.strftime("%Y-%m-%d %H:%M:%S.%3N")
|
|
20
|
+
"[#{timestamp}] #{severity} [#{progname}] #{msg}\n"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
8
23
|
|
|
9
|
-
server = ModelContextProtocol::Server.
|
|
24
|
+
server = ModelContextProtocol::Server.with_stdio_transport do |config|
|
|
10
25
|
config.name = "MCP Development Server"
|
|
11
26
|
config.version = "1.0.0"
|
|
12
|
-
config.logging_enabled = true
|
|
13
27
|
|
|
14
28
|
config.pagination = {
|
|
15
|
-
default_page_size:
|
|
16
|
-
max_page_size:
|
|
29
|
+
default_page_size: 2,
|
|
30
|
+
max_page_size: 3,
|
|
17
31
|
cursor_ttl: 1800
|
|
18
32
|
}
|
|
19
33
|
|
|
@@ -24,15 +38,17 @@ server = ModelContextProtocol::Server.new do |config|
|
|
|
24
38
|
request_id: SecureRandom.uuid
|
|
25
39
|
}
|
|
26
40
|
|
|
27
|
-
config.registry
|
|
41
|
+
config.registry do
|
|
28
42
|
prompts list_changed: true do
|
|
29
43
|
register TestPrompt
|
|
44
|
+
register TestPromptWithCompletionClass
|
|
30
45
|
end
|
|
31
46
|
|
|
32
47
|
resources list_changed: true, subscribe: true do
|
|
33
48
|
register TestResource
|
|
34
49
|
register TestAnnotatedResource
|
|
35
50
|
register TestBinaryResource
|
|
51
|
+
register TestProgressiveResource
|
|
36
52
|
end
|
|
37
53
|
|
|
38
54
|
resource_templates do
|
|
@@ -40,11 +56,17 @@ server = ModelContextProtocol::Server.new do |config|
|
|
|
40
56
|
end
|
|
41
57
|
|
|
42
58
|
tools list_changed: true do
|
|
59
|
+
register TestToolWithStructuredContentResponse
|
|
43
60
|
register TestToolWithTextResponse
|
|
44
61
|
register TestToolWithImageResponse
|
|
45
|
-
register
|
|
62
|
+
register TestToolWithMixedContentResponse
|
|
46
63
|
register TestToolWithResourceResponse
|
|
47
64
|
register TestToolWithToolErrorResponse
|
|
65
|
+
register TestToolWithCancellableSleep
|
|
66
|
+
register TestToolWithProgressableAndCancellable
|
|
67
|
+
register TestToolWithAnnotations
|
|
68
|
+
register TestToolWithSecuritySchemes
|
|
69
|
+
register TestToolWithResourceLinkResponse
|
|
48
70
|
end
|
|
49
71
|
end
|
|
50
72
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: model-context-protocol-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dick Davis
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-03-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json-schema
|
|
@@ -58,14 +58,14 @@ dependencies:
|
|
|
58
58
|
requirements:
|
|
59
59
|
- - "~>"
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '
|
|
61
|
+
version: '3.0'
|
|
62
62
|
type: :runtime
|
|
63
63
|
prerelease: false
|
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
65
|
requirements:
|
|
66
66
|
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '
|
|
68
|
+
version: '3.0'
|
|
69
69
|
- !ruby/object:Gem::Dependency
|
|
70
70
|
name: concurrent-ruby
|
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -82,7 +82,7 @@ dependencies:
|
|
|
82
82
|
version: '1.3'
|
|
83
83
|
description:
|
|
84
84
|
email:
|
|
85
|
-
- dick
|
|
85
|
+
- webmaster@dick.codes
|
|
86
86
|
executables: []
|
|
87
87
|
extensions: []
|
|
88
88
|
extra_rdoc_files: []
|
|
@@ -97,13 +97,34 @@ files:
|
|
|
97
97
|
- README.md
|
|
98
98
|
- Rakefile
|
|
99
99
|
- lib/model_context_protocol.rb
|
|
100
|
+
- lib/model_context_protocol/rspec.rb
|
|
101
|
+
- lib/model_context_protocol/rspec/helpers.rb
|
|
102
|
+
- lib/model_context_protocol/rspec/matchers.rb
|
|
103
|
+
- lib/model_context_protocol/rspec/matchers/be_mcp_error_response.rb
|
|
104
|
+
- lib/model_context_protocol/rspec/matchers/be_valid_mcp_class.rb
|
|
105
|
+
- lib/model_context_protocol/rspec/matchers/be_valid_mcp_prompt_response.rb
|
|
106
|
+
- lib/model_context_protocol/rspec/matchers/be_valid_mcp_resource_response.rb
|
|
107
|
+
- lib/model_context_protocol/rspec/matchers/be_valid_mcp_tool_response.rb
|
|
108
|
+
- lib/model_context_protocol/rspec/matchers/have_audio_content.rb
|
|
109
|
+
- lib/model_context_protocol/rspec/matchers/have_embedded_resource_content.rb
|
|
110
|
+
- lib/model_context_protocol/rspec/matchers/have_image_content.rb
|
|
111
|
+
- lib/model_context_protocol/rspec/matchers/have_message_count.rb
|
|
112
|
+
- lib/model_context_protocol/rspec/matchers/have_message_with_role.rb
|
|
113
|
+
- lib/model_context_protocol/rspec/matchers/have_resource_annotations.rb
|
|
114
|
+
- lib/model_context_protocol/rspec/matchers/have_resource_blob.rb
|
|
115
|
+
- lib/model_context_protocol/rspec/matchers/have_resource_link_content.rb
|
|
116
|
+
- lib/model_context_protocol/rspec/matchers/have_resource_mime_type.rb
|
|
117
|
+
- lib/model_context_protocol/rspec/matchers/have_resource_text.rb
|
|
118
|
+
- lib/model_context_protocol/rspec/matchers/have_structured_content.rb
|
|
119
|
+
- lib/model_context_protocol/rspec/matchers/have_text_content.rb
|
|
100
120
|
- lib/model_context_protocol/server.rb
|
|
101
121
|
- lib/model_context_protocol/server/cancellable.rb
|
|
122
|
+
- lib/model_context_protocol/server/client_logger.rb
|
|
102
123
|
- lib/model_context_protocol/server/completion.rb
|
|
103
124
|
- lib/model_context_protocol/server/configuration.rb
|
|
104
125
|
- lib/model_context_protocol/server/content.rb
|
|
105
126
|
- lib/model_context_protocol/server/content_helpers.rb
|
|
106
|
-
- lib/model_context_protocol/server/
|
|
127
|
+
- lib/model_context_protocol/server/global_config/server_logging.rb
|
|
107
128
|
- lib/model_context_protocol/server/pagination.rb
|
|
108
129
|
- lib/model_context_protocol/server/progressable.rb
|
|
109
130
|
- lib/model_context_protocol/server/prompt.rb
|
|
@@ -114,19 +135,25 @@ files:
|
|
|
114
135
|
- lib/model_context_protocol/server/resource.rb
|
|
115
136
|
- lib/model_context_protocol/server/resource_template.rb
|
|
116
137
|
- lib/model_context_protocol/server/router.rb
|
|
138
|
+
- lib/model_context_protocol/server/server_logger.rb
|
|
139
|
+
- lib/model_context_protocol/server/stdio_configuration.rb
|
|
117
140
|
- lib/model_context_protocol/server/stdio_transport.rb
|
|
118
141
|
- lib/model_context_protocol/server/stdio_transport/request_store.rb
|
|
142
|
+
- lib/model_context_protocol/server/streamable_http_configuration.rb
|
|
119
143
|
- lib/model_context_protocol/server/streamable_http_transport.rb
|
|
120
144
|
- lib/model_context_protocol/server/streamable_http_transport/event_counter.rb
|
|
121
145
|
- lib/model_context_protocol/server/streamable_http_transport/message_poller.rb
|
|
122
146
|
- lib/model_context_protocol/server/streamable_http_transport/notification_queue.rb
|
|
123
147
|
- lib/model_context_protocol/server/streamable_http_transport/request_store.rb
|
|
148
|
+
- lib/model_context_protocol/server/streamable_http_transport/server_request_store.rb
|
|
124
149
|
- lib/model_context_protocol/server/streamable_http_transport/session_message_queue.rb
|
|
125
150
|
- lib/model_context_protocol/server/streamable_http_transport/session_store.rb
|
|
126
151
|
- lib/model_context_protocol/server/streamable_http_transport/stream_registry.rb
|
|
127
152
|
- lib/model_context_protocol/server/tool.rb
|
|
128
153
|
- lib/model_context_protocol/version.rb
|
|
154
|
+
- lib/puma/plugin/mcp.rb
|
|
129
155
|
- tasks/mcp.rake
|
|
156
|
+
- tasks/templates/dev-http-puma.erb
|
|
130
157
|
- tasks/templates/dev-http.erb
|
|
131
158
|
- tasks/templates/dev.erb
|
|
132
159
|
homepage: https://github.com/dickdavis/model-context-protocol-rb
|