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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -1
  3. data/README.md +181 -950
  4. data/lib/model_context_protocol/rspec/helpers.rb +54 -0
  5. data/lib/model_context_protocol/rspec/matchers/be_mcp_error_response.rb +123 -0
  6. data/lib/model_context_protocol/rspec/matchers/be_valid_mcp_class.rb +103 -0
  7. data/lib/model_context_protocol/rspec/matchers/be_valid_mcp_prompt_response.rb +126 -0
  8. data/lib/model_context_protocol/rspec/matchers/be_valid_mcp_resource_response.rb +121 -0
  9. data/lib/model_context_protocol/rspec/matchers/be_valid_mcp_tool_response.rb +135 -0
  10. data/lib/model_context_protocol/rspec/matchers/have_audio_content.rb +109 -0
  11. data/lib/model_context_protocol/rspec/matchers/have_embedded_resource_content.rb +150 -0
  12. data/lib/model_context_protocol/rspec/matchers/have_image_content.rb +109 -0
  13. data/lib/model_context_protocol/rspec/matchers/have_message_count.rb +87 -0
  14. data/lib/model_context_protocol/rspec/matchers/have_message_with_role.rb +152 -0
  15. data/lib/model_context_protocol/rspec/matchers/have_resource_annotations.rb +135 -0
  16. data/lib/model_context_protocol/rspec/matchers/have_resource_blob.rb +108 -0
  17. data/lib/model_context_protocol/rspec/matchers/have_resource_link_content.rb +138 -0
  18. data/lib/model_context_protocol/rspec/matchers/have_resource_mime_type.rb +103 -0
  19. data/lib/model_context_protocol/rspec/matchers/have_resource_text.rb +112 -0
  20. data/lib/model_context_protocol/rspec/matchers/have_structured_content.rb +88 -0
  21. data/lib/model_context_protocol/rspec/matchers/have_text_content.rb +113 -0
  22. data/lib/model_context_protocol/rspec/matchers.rb +31 -0
  23. data/lib/model_context_protocol/rspec.rb +23 -0
  24. data/lib/model_context_protocol/server/cancellable.rb +5 -5
  25. data/lib/model_context_protocol/server/{mcp_logger.rb → client_logger.rb} +8 -11
  26. data/lib/model_context_protocol/server/configuration.rb +196 -109
  27. data/lib/model_context_protocol/server/content_helpers.rb +1 -1
  28. data/lib/model_context_protocol/server/global_config/server_logging.rb +78 -0
  29. data/lib/model_context_protocol/server/progressable.rb +43 -21
  30. data/lib/model_context_protocol/server/prompt.rb +12 -21
  31. data/lib/model_context_protocol/server/redis_client_proxy.rb +2 -14
  32. data/lib/model_context_protocol/server/redis_config.rb +5 -7
  33. data/lib/model_context_protocol/server/redis_pool_manager.rb +11 -14
  34. data/lib/model_context_protocol/server/registry.rb +8 -0
  35. data/lib/model_context_protocol/server/resource.rb +7 -4
  36. data/lib/model_context_protocol/server/router.rb +285 -9
  37. data/lib/model_context_protocol/server/server_logger.rb +31 -0
  38. data/lib/model_context_protocol/server/stdio_configuration.rb +114 -0
  39. data/lib/model_context_protocol/server/stdio_transport/request_store.rb +12 -53
  40. data/lib/model_context_protocol/server/stdio_transport.rb +18 -12
  41. data/lib/model_context_protocol/server/streamable_http_configuration.rb +218 -0
  42. data/lib/model_context_protocol/server/streamable_http_transport/event_counter.rb +0 -13
  43. data/lib/model_context_protocol/server/streamable_http_transport/message_poller.rb +9 -9
  44. data/lib/model_context_protocol/server/streamable_http_transport/notification_queue.rb +0 -41
  45. data/lib/model_context_protocol/server/streamable_http_transport/request_store.rb +21 -124
  46. data/lib/model_context_protocol/server/streamable_http_transport/server_request_store.rb +167 -0
  47. data/lib/model_context_protocol/server/streamable_http_transport/session_message_queue.rb +0 -58
  48. data/lib/model_context_protocol/server/streamable_http_transport/session_store.rb +17 -31
  49. data/lib/model_context_protocol/server/streamable_http_transport/stream_registry.rb +0 -34
  50. data/lib/model_context_protocol/server/streamable_http_transport.rb +589 -215
  51. data/lib/model_context_protocol/server/tool.rb +73 -6
  52. data/lib/model_context_protocol/server.rb +204 -261
  53. data/lib/model_context_protocol/version.rb +1 -1
  54. data/lib/model_context_protocol.rb +4 -1
  55. data/lib/puma/plugin/mcp.rb +39 -0
  56. data/tasks/mcp.rake +26 -0
  57. data/tasks/templates/dev-http-puma.erb +251 -0
  58. data/tasks/templates/dev-http.erb +166 -184
  59. data/tasks/templates/dev.erb +29 -7
  60. 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 'stringio'
14
+ require "stringio"
14
15
 
15
16
  require_relative "../lib/model_context_protocol"
16
17
 
17
- ModelContextProtocol::Server.configure_redis do |config|
18
- config.redis_url = "redis://localhost:6379/0"
19
- config.pool_size = 10
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
- Dir[File.join(__dir__, "../spec/support/**/*.rb")].each { |file| require file }
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
- logger = Logger.new(STDOUT)
28
- logger.level = Logger::INFO
29
- logger.formatter = proc do |severity, datetime, progname, msg|
30
- request_id = Thread.current[:request_id] || "----"
31
- "[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{severity} [#{request_id}]: #{msg}\n"
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(logger)
37
- @logger = 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
- if session_id
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
- if request.request_method == "OPTIONS"
99
- return [200, {
100
- "Access-Control-Allow-Origin" => "*",
101
- "Access-Control-Allow-Methods" => "GET, POST, DELETE, OPTIONS",
102
- "Access-Control-Allow-Headers" => "Content-Type, Accept, Mcp-Session-Id, MCP-Protocol-Version, Origin",
103
- "Access-Control-Max-Age" => "86400"
104
- }, [""]]
105
- end
106
-
107
- transport_config = {
108
- type: :streamable_http,
109
- env:,
110
- require_sessions: false,
111
- session_ttl: 3600,
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("← Unexpected response format")
178
- [500, {"Content-Type" => "application/json"}, ['{"error": "Unexpected response format"}']]
191
+ @logger.error("← Invalid transport response")
192
+ [500, {"Content-Type" => "application/json"}, ['{"error": "Invalid transport response"}']]
179
193
  end
180
- rescue => e
181
- @logger.error("Error handling request: #{e.message}")
182
- @logger.debug("Full backtrace:\n#{e.backtrace.join("\n")}")
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
- private
191
-
192
- def create_mcp_server(transport_config)
193
- ModelContextProtocol::Server.new do |config|
194
- config.name = "MCP Development Server"
195
- config.version = "1.0.0"
196
- config.logging_enabled = true
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
- logger.info("Starting MCP #{protocol.upcase} Development Server on #{protocol}://localhost:#{port}/mcp")
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(logger)
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
- logger.error("SSL certificates not found at tmp/ssl/")
266
- logger.error("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\"")
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 = WEBrick::HTTPServer.new(server_options)
279
- server.mount '/', Rackup::Handler::WEBrick, app
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
- server.shutdown
284
- exit(0)
265
+ ModelContextProtocol::Server.shutdown
266
+ webrick_server.shutdown
285
267
  end
286
268
  end
287
269
 
288
- server.start
270
+ webrick_server.start
@@ -4,16 +4,30 @@ require "bundler/setup"
4
4
  require "securerandom"
5
5
  require_relative "../lib/model_context_protocol"
6
6
 
7
- Dir[File.join(__dir__, "../spec/support/**/*.rb")].each { |file| require file }
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.new do |config|
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: 10,
16
- max_page_size: 20,
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 = ModelContextProtocol::Server::Registry.new do
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 TestToolWithImageResponseDefaultMimeType
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.5.1
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: 2025-09-23 00:00:00.000000000 Z
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: '2.4'
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: '2.4'
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@hey.com
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/mcp_logger.rb
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