tsikol 0.1.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 (75) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +22 -0
  3. data/CONTRIBUTING.md +84 -0
  4. data/LICENSE +21 -0
  5. data/README.md +579 -0
  6. data/Rakefile +12 -0
  7. data/docs/README.md +69 -0
  8. data/docs/api/middleware.md +721 -0
  9. data/docs/api/prompt.md +858 -0
  10. data/docs/api/resource.md +651 -0
  11. data/docs/api/server.md +509 -0
  12. data/docs/api/test-helpers.md +591 -0
  13. data/docs/api/tool.md +527 -0
  14. data/docs/cookbook/authentication.md +651 -0
  15. data/docs/cookbook/caching.md +877 -0
  16. data/docs/cookbook/dynamic-tools.md +970 -0
  17. data/docs/cookbook/error-handling.md +887 -0
  18. data/docs/cookbook/logging.md +1044 -0
  19. data/docs/cookbook/rate-limiting.md +717 -0
  20. data/docs/examples/code-assistant.md +922 -0
  21. data/docs/examples/complete-server.md +726 -0
  22. data/docs/examples/database-manager.md +1198 -0
  23. data/docs/examples/devops-tools.md +1382 -0
  24. data/docs/examples/echo-server.md +501 -0
  25. data/docs/examples/weather-service.md +822 -0
  26. data/docs/guides/completion.md +472 -0
  27. data/docs/guides/getting-started.md +462 -0
  28. data/docs/guides/middleware.md +823 -0
  29. data/docs/guides/project-structure.md +434 -0
  30. data/docs/guides/prompts.md +920 -0
  31. data/docs/guides/resources.md +720 -0
  32. data/docs/guides/sampling.md +804 -0
  33. data/docs/guides/testing.md +863 -0
  34. data/docs/guides/tools.md +627 -0
  35. data/examples/README.md +92 -0
  36. data/examples/advanced_features.rb +129 -0
  37. data/examples/basic-migrated/app/prompts/weather_chat.rb +44 -0
  38. data/examples/basic-migrated/app/resources/weather_alerts.rb +18 -0
  39. data/examples/basic-migrated/app/tools/get_current_weather.rb +34 -0
  40. data/examples/basic-migrated/app/tools/get_forecast.rb +30 -0
  41. data/examples/basic-migrated/app/tools/get_weather_by_coords.rb +48 -0
  42. data/examples/basic-migrated/server.rb +25 -0
  43. data/examples/basic.rb +73 -0
  44. data/examples/full_featured.rb +175 -0
  45. data/examples/middleware_example.rb +112 -0
  46. data/examples/sampling_example.rb +104 -0
  47. data/examples/weather-service/app/prompts/weather/chat.rb +90 -0
  48. data/examples/weather-service/app/resources/weather/alerts.rb +59 -0
  49. data/examples/weather-service/app/tools/weather/get_current.rb +82 -0
  50. data/examples/weather-service/app/tools/weather/get_forecast.rb +90 -0
  51. data/examples/weather-service/server.rb +28 -0
  52. data/exe/tsikol +6 -0
  53. data/lib/tsikol/cli/templates/Gemfile.erb +10 -0
  54. data/lib/tsikol/cli/templates/README.md.erb +38 -0
  55. data/lib/tsikol/cli/templates/gitignore.erb +49 -0
  56. data/lib/tsikol/cli/templates/prompt.rb.erb +53 -0
  57. data/lib/tsikol/cli/templates/resource.rb.erb +29 -0
  58. data/lib/tsikol/cli/templates/server.rb.erb +24 -0
  59. data/lib/tsikol/cli/templates/tool.rb.erb +60 -0
  60. data/lib/tsikol/cli.rb +203 -0
  61. data/lib/tsikol/error_handler.rb +141 -0
  62. data/lib/tsikol/health.rb +198 -0
  63. data/lib/tsikol/http_transport.rb +72 -0
  64. data/lib/tsikol/lifecycle.rb +149 -0
  65. data/lib/tsikol/middleware.rb +168 -0
  66. data/lib/tsikol/prompt.rb +101 -0
  67. data/lib/tsikol/resource.rb +53 -0
  68. data/lib/tsikol/router.rb +190 -0
  69. data/lib/tsikol/server.rb +660 -0
  70. data/lib/tsikol/stdio_transport.rb +108 -0
  71. data/lib/tsikol/test_helpers.rb +261 -0
  72. data/lib/tsikol/tool.rb +111 -0
  73. data/lib/tsikol/version.rb +5 -0
  74. data/lib/tsikol.rb +72 -0
  75. metadata +219 -0
@@ -0,0 +1,726 @@
1
+ # Complete MCP Server Example
2
+
3
+ This example demonstrates all Tsikol features in a production-ready MCP server.
4
+
5
+ ## Full Implementation
6
+
7
+ ```ruby
8
+ #!/usr/bin/env ruby
9
+ # frozen_string_literal: true
10
+
11
+ require 'bundler/setup'
12
+ require 'tsikol'
13
+
14
+ # Complete MCP server with all features
15
+ Tsikol.start(name: "complete-mcp-server") do
16
+ # Load components from files
17
+ require_relative 'app/tools/code_analyzer'
18
+ require_relative 'app/tools/file_manager'
19
+ require_relative 'app/tools/ai_assistant'
20
+ require_relative 'app/resources/project_info'
21
+ require_relative 'app/resources/system_status'
22
+ require_relative 'app/prompts/development_chat'
23
+
24
+ # Register components
25
+ tool CodeAnalyzer
26
+ tool FileManager
27
+ tool AiAssistant
28
+ resource ProjectInfo
29
+ resource SystemStatus
30
+ prompt DevelopmentChat
31
+
32
+ # Enable all capabilities
33
+ logging true
34
+ completion true
35
+
36
+ # Configure sampling for AI features
37
+ on_sampling do |request|
38
+ # In production, the MCP client handles this
39
+ # This is for testing/development
40
+ {
41
+ role: "assistant",
42
+ content: {
43
+ type: "text",
44
+ text: "AI response for: #{request[:messages].last[:content][:text]}"
45
+ }
46
+ }
47
+ end
48
+
49
+ # Add middleware stack
50
+ use Tsikol::ValidationMiddleware
51
+ use Tsikol::LoggingMiddleware
52
+ use Tsikol::RateLimitMiddleware, max_requests: 100, window: 60
53
+ use AuthenticationMiddleware
54
+ use MetricsMiddleware
55
+
56
+ # Lifecycle hooks
57
+ before_start do
58
+ log :info, "Initializing complete MCP server..."
59
+ @start_time = Time.now
60
+ load_configuration
61
+ initialize_connections
62
+ end
63
+
64
+ after_start do
65
+ log :info, "Server ready!", data: {
66
+ tools: @tool_instances.keys,
67
+ resources: @resource_instances.keys,
68
+ capabilities: @server_capabilities.keys
69
+ }
70
+ end
71
+
72
+ before_stop do
73
+ log :info, "Shutting down gracefully..."
74
+ save_state
75
+ close_connections
76
+ end
77
+
78
+ after_stop do
79
+ uptime = Time.now - @start_time
80
+ log :info, "Server stopped", data: { uptime: uptime }
81
+ end
82
+
83
+ # Tool-specific hooks
84
+ before_tool do |tool_name, params|
85
+ log :debug, "Executing tool: #{tool_name}", data: { params: params }
86
+ @metrics.increment("tools.#{tool_name}.calls")
87
+ end
88
+
89
+ after_tool do |tool_name, params, result|
90
+ log :debug, "Tool completed: #{tool_name}"
91
+ track_tool_usage(tool_name, result)
92
+ end
93
+
94
+ # Error handling for specific tools
95
+ before_tool "ai_assistant" do |params|
96
+ validate_ai_request(params)
97
+ end
98
+
99
+ # Inline tools for simple operations
100
+ tool "ping" do
101
+ "pong"
102
+ end
103
+
104
+ tool "echo" do |message:|
105
+ message
106
+ end
107
+
108
+ # Inline resources
109
+ resource "health" do
110
+ {
111
+ status: "healthy",
112
+ uptime: Time.now - @start_time,
113
+ version: Tsikol::VERSION
114
+ }.to_json
115
+ end
116
+
117
+ # Helper methods
118
+ private
119
+
120
+ def load_configuration
121
+ # Load config from file or environment
122
+ @config = {
123
+ max_file_size: ENV.fetch('MAX_FILE_SIZE', 1024 * 1024).to_i,
124
+ allowed_extensions: %w[rb py js ts go rs java c cpp md txt json yaml],
125
+ ai_model: ENV.fetch('AI_MODEL', 'claude-3-sonnet')
126
+ }
127
+ end
128
+
129
+ def initialize_connections
130
+ # Initialize any external connections
131
+ @cache = {}
132
+ @metrics = Tsikol::Metrics.new
133
+ end
134
+
135
+ def save_state
136
+ # Save any persistent state
137
+ File.write('server_state.json', {
138
+ last_shutdown: Time.now,
139
+ total_requests: @metrics.get(:requests_total)
140
+ }.to_json)
141
+ end
142
+
143
+ def close_connections
144
+ # Clean up connections
145
+ @cache.clear
146
+ end
147
+
148
+ def track_tool_usage(tool_name, result)
149
+ # Track metrics
150
+ @metrics.increment("tools.#{tool_name}.success") if result
151
+ end
152
+
153
+ def validate_ai_request(params)
154
+ # Validate AI requests
155
+ raise "Message too long" if params[:message]&.length > 10000
156
+ end
157
+ end
158
+
159
+ # Custom middleware
160
+ class AuthenticationMiddleware < Tsikol::Middleware
161
+ def before_request(message)
162
+ # Skip auth for certain methods
163
+ return message if %w[initialize ping health tools/list].include?(message["method"])
164
+
165
+ # Check authentication
166
+ token = message.dig("metadata", "auth_token")
167
+ unless valid_token?(token)
168
+ raise Tsikol::AuthenticationError, "Invalid authentication token"
169
+ end
170
+
171
+ message
172
+ end
173
+
174
+ private
175
+
176
+ def valid_token?(token)
177
+ # In production, validate against real auth system
178
+ token == ENV['MCP_AUTH_TOKEN']
179
+ end
180
+ end
181
+
182
+ class MetricsMiddleware < Tsikol::Middleware
183
+ def initialize(app)
184
+ super
185
+ @metrics = {}
186
+ end
187
+
188
+ def before_request(message)
189
+ message["_start_time"] = Time.now
190
+ message
191
+ end
192
+
193
+ def after_response(response, original_message)
194
+ duration = Time.now - original_message["_start_time"]
195
+ track_metric(original_message["method"], duration)
196
+ response
197
+ end
198
+
199
+ private
200
+
201
+ def track_metric(method, duration)
202
+ @metrics[method] ||= []
203
+ @metrics[method] << duration
204
+
205
+ # Log slow requests
206
+ if duration > 1.0
207
+ log :warning, "Slow request", data: {
208
+ method: method,
209
+ duration: duration
210
+ }
211
+ end
212
+ end
213
+ end
214
+ ```
215
+
216
+ ## Component Files
217
+
218
+ ### app/tools/code_analyzer.rb
219
+
220
+ ```ruby
221
+ # frozen_string_literal: true
222
+
223
+ class CodeAnalyzer < Tsikol::Tool
224
+ description "Analyze code quality and complexity"
225
+
226
+ parameter :file_path do
227
+ type :string
228
+ required
229
+ description "Path to file to analyze"
230
+
231
+ complete do |partial|
232
+ Dir.glob("#{partial}*").select { |f|
233
+ f.match?(/\.(rb|py|js|ts|go)$/)
234
+ }
235
+ end
236
+ end
237
+
238
+ parameter :analysis_type do
239
+ type :string
240
+ optional
241
+ default "all"
242
+ enum ["complexity", "style", "security", "performance", "all"]
243
+
244
+ complete do |partial|
245
+ ["complexity", "style", "security", "performance", "all"]
246
+ .select { |t| t.start_with?(partial) }
247
+ end
248
+ end
249
+
250
+ def execute(file_path:, analysis_type: "all")
251
+ unless File.exist?(file_path)
252
+ raise Tsikol::ValidationError, "File not found: #{file_path}"
253
+ end
254
+
255
+ log :info, "Analyzing #{file_path}", data: { type: analysis_type }
256
+
257
+ code = File.read(file_path)
258
+ results = {}
259
+
260
+ if analysis_type == "all" || analysis_type == "complexity"
261
+ results[:complexity] = analyze_complexity(code)
262
+ end
263
+
264
+ if analysis_type == "all" || analysis_type == "style"
265
+ results[:style] = analyze_style(code)
266
+ end
267
+
268
+ if analysis_type == "all" || analysis_type == "security"
269
+ results[:security] = analyze_security(code)
270
+ end
271
+
272
+ if analysis_type == "all" || analysis_type == "performance"
273
+ results[:performance] = analyze_performance(code)
274
+ end
275
+
276
+ format_results(results)
277
+ end
278
+
279
+ private
280
+
281
+ def analyze_complexity(code)
282
+ lines = code.lines.count
283
+ methods = code.scan(/def\s+\w+/).count
284
+ classes = code.scan(/class\s+\w+/).count
285
+
286
+ {
287
+ lines_of_code: lines,
288
+ methods: methods,
289
+ classes: classes,
290
+ complexity_score: calculate_complexity_score(code)
291
+ }
292
+ end
293
+
294
+ def analyze_style(code)
295
+ issues = []
296
+
297
+ # Check line length
298
+ code.lines.each_with_index do |line, index|
299
+ if line.chomp.length > 100
300
+ issues << "Line #{index + 1}: Too long (#{line.chomp.length} chars)"
301
+ end
302
+ end
303
+
304
+ # Check indentation
305
+ if code.match?(/\t/)
306
+ issues << "Uses tabs instead of spaces"
307
+ end
308
+
309
+ {
310
+ style_issues: issues,
311
+ passed: issues.empty?
312
+ }
313
+ end
314
+
315
+ def analyze_security(code)
316
+ vulnerabilities = []
317
+
318
+ # Check for common security issues
319
+ if code.match?(/eval\s*\(/)
320
+ vulnerabilities << "Uses eval() - potential security risk"
321
+ end
322
+
323
+ if code.match?(/system\s*\(|`.*`/)
324
+ vulnerabilities << "Executes system commands - verify input sanitization"
325
+ end
326
+
327
+ {
328
+ vulnerabilities: vulnerabilities,
329
+ secure: vulnerabilities.empty?
330
+ }
331
+ end
332
+
333
+ def analyze_performance(code)
334
+ suggestions = []
335
+
336
+ # Look for performance anti-patterns
337
+ if code.match?(/\.select.*\.first/)
338
+ suggestions << "Use .find instead of .select.first"
339
+ end
340
+
341
+ if code.match?(/\+\s*=.*loop/)
342
+ suggestions << "String concatenation in loop - consider using array.join"
343
+ end
344
+
345
+ {
346
+ suggestions: suggestions,
347
+ optimized: suggestions.empty?
348
+ }
349
+ end
350
+
351
+ def calculate_complexity_score(code)
352
+ # Simple cyclomatic complexity estimate
353
+ score = 1
354
+ score += code.scan(/\bif\b|\belsif\b|\bcase\b|\bwhen\b/).count
355
+ score += code.scan(/\bwhile\b|\bfor\b|\buntil\b/).count
356
+ score += code.scan(/\brescue\b/).count
357
+ score
358
+ end
359
+
360
+ def format_results(results)
361
+ output = ["Code Analysis Results", "=" * 50]
362
+
363
+ results.each do |type, data|
364
+ output << "\n#{type.to_s.capitalize} Analysis:"
365
+ output << JSON.pretty_generate(data)
366
+ end
367
+
368
+ output.join("\n")
369
+ end
370
+
371
+ def set_server(server)
372
+ @server = server
373
+ define_singleton_method(:log) do |level, message, data: nil|
374
+ @server.log(level, message, data: data)
375
+ end
376
+ end
377
+ end
378
+ ```
379
+
380
+ ### app/tools/file_manager.rb
381
+
382
+ ```ruby
383
+ # frozen_string_literal: true
384
+
385
+ class FileManager < Tsikol::Tool
386
+ description "Manage project files"
387
+
388
+ parameter :action do
389
+ type :string
390
+ required
391
+ enum ["list", "read", "create", "update", "delete", "search"]
392
+
393
+ complete do |partial|
394
+ ["list", "read", "create", "update", "delete", "search"]
395
+ .select { |a| a.start_with?(partial) }
396
+ end
397
+ end
398
+
399
+ parameter :path do
400
+ type :string
401
+ required
402
+ description "File or directory path"
403
+
404
+ complete do |partial|
405
+ Dir.glob("#{partial}*")
406
+ end
407
+ end
408
+
409
+ parameter :content do
410
+ type :string
411
+ optional
412
+ description "Content for create/update operations"
413
+ end
414
+
415
+ parameter :pattern do
416
+ type :string
417
+ optional
418
+ description "Search pattern for search operation"
419
+ end
420
+
421
+ def execute(action:, path:, content: nil, pattern: nil)
422
+ case action
423
+ when "list"
424
+ list_directory(path)
425
+ when "read"
426
+ read_file(path)
427
+ when "create"
428
+ create_file(path, content)
429
+ when "update"
430
+ update_file(path, content)
431
+ when "delete"
432
+ delete_file(path)
433
+ when "search"
434
+ search_files(path, pattern)
435
+ else
436
+ raise Tsikol::ValidationError, "Unknown action: #{action}"
437
+ end
438
+ end
439
+
440
+ private
441
+
442
+ def list_directory(path)
443
+ unless File.directory?(path)
444
+ raise Tsikol::ValidationError, "Not a directory: #{path}"
445
+ end
446
+
447
+ entries = Dir.entries(path).reject { |e| e.start_with?('.') }
448
+
449
+ entries.map do |entry|
450
+ full_path = File.join(path, entry)
451
+ {
452
+ name: entry,
453
+ type: File.directory?(full_path) ? "directory" : "file",
454
+ size: File.size(full_path),
455
+ modified: File.mtime(full_path).iso8601
456
+ }
457
+ end.to_json
458
+ end
459
+
460
+ def read_file(path)
461
+ unless File.exist?(path)
462
+ raise Tsikol::ValidationError, "File not found: #{path}"
463
+ end
464
+
465
+ File.read(path)
466
+ end
467
+
468
+ def create_file(path, content)
469
+ if File.exist?(path)
470
+ raise Tsikol::ValidationError, "File already exists: #{path}"
471
+ end
472
+
473
+ File.write(path, content || "")
474
+ "File created: #{path}"
475
+ end
476
+
477
+ def update_file(path, content)
478
+ unless File.exist?(path)
479
+ raise Tsikol::ValidationError, "File not found: #{path}"
480
+ end
481
+
482
+ File.write(path, content)
483
+ "File updated: #{path}"
484
+ end
485
+
486
+ def delete_file(path)
487
+ unless File.exist?(path)
488
+ raise Tsikol::ValidationError, "File not found: #{path}"
489
+ end
490
+
491
+ File.delete(path)
492
+ "File deleted: #{path}"
493
+ end
494
+
495
+ def search_files(path, pattern)
496
+ unless pattern
497
+ raise Tsikol::ValidationError, "Pattern required for search"
498
+ end
499
+
500
+ matches = []
501
+
502
+ Dir.glob("#{path}/**/*").each do |file|
503
+ next if File.directory?(file)
504
+
505
+ begin
506
+ content = File.read(file)
507
+ if content.match?(Regexp.new(pattern, Regexp::IGNORECASE))
508
+ matches << {
509
+ file: file,
510
+ matches: content.lines.select { |line|
511
+ line.match?(Regexp.new(pattern, Regexp::IGNORECASE))
512
+ }.first(3)
513
+ }
514
+ end
515
+ rescue => e
516
+ log :warning, "Error reading #{file}", data: { error: e.message }
517
+ end
518
+ end
519
+
520
+ matches.to_json
521
+ end
522
+
523
+ def set_server(server)
524
+ @server = server
525
+ define_singleton_method(:log) do |level, message, data: nil|
526
+ @server.log(level, message, data: data)
527
+ end
528
+ end
529
+ end
530
+ ```
531
+
532
+ ### app/resources/system_status.rb
533
+
534
+ ```ruby
535
+ # frozen_string_literal: true
536
+
537
+ class SystemStatus < Tsikol::Resource
538
+ uri "system/status"
539
+ description "Current system status and metrics"
540
+
541
+ def read
542
+ {
543
+ server: {
544
+ name: "complete-mcp-server",
545
+ version: Tsikol::VERSION,
546
+ protocol_version: Tsikol::PROTOCOL_VERSION,
547
+ uptime: calculate_uptime
548
+ },
549
+ system: {
550
+ ruby_version: RUBY_VERSION,
551
+ platform: RUBY_PLATFORM,
552
+ pid: Process.pid,
553
+ memory_usage: get_memory_usage
554
+ },
555
+ metrics: {
556
+ total_requests: @server.metrics.get(:requests_total),
557
+ active_requests: @server.metrics.get(:active_requests),
558
+ error_rate: calculate_error_rate,
559
+ average_response_time: @server.metrics.average(:response_time)
560
+ },
561
+ capabilities: @server.instance_variable_get(:@server_capabilities).keys,
562
+ health: determine_health_status
563
+ }.to_json
564
+ end
565
+
566
+ private
567
+
568
+ def calculate_uptime
569
+ start_time = @server.instance_variable_get(:@start_time)
570
+ return 0 unless start_time
571
+ Time.now - start_time
572
+ end
573
+
574
+ def get_memory_usage
575
+ # Simple memory usage estimate
576
+ "#{(GC.stat[:heap_live_slots] * 40.0 / 1024 / 1024).round(2)}MB"
577
+ end
578
+
579
+ def calculate_error_rate
580
+ total = @server.metrics.get(:requests_total)
581
+ errors = @server.metrics.get(:errors_total)
582
+
583
+ return 0.0 if total == 0
584
+ (errors.to_f / total * 100).round(2)
585
+ end
586
+
587
+ def determine_health_status
588
+ error_rate = calculate_error_rate
589
+
590
+ if error_rate > 50
591
+ "critical"
592
+ elsif error_rate > 10
593
+ "degraded"
594
+ else
595
+ "healthy"
596
+ end
597
+ end
598
+
599
+ def set_server(server)
600
+ @server = server
601
+ end
602
+ end
603
+ ```
604
+
605
+ ## Testing the Server
606
+
607
+ ```ruby
608
+ require 'minitest/autorun'
609
+ require 'tsikol/test_helpers'
610
+
611
+ class CompleteServerTest < Minitest::Test
612
+ include Tsikol::TestHelpers::Assertions
613
+
614
+ def setup
615
+ # Load the server
616
+ require_relative '../server'
617
+ @server = $tsikol_server # Set by server.rb
618
+ @client = Tsikol::TestHelpers::TestClient.new(@server)
619
+
620
+ # Initialize connection
621
+ @client.initialize_connection
622
+ end
623
+
624
+ def test_ping
625
+ response = @client.request("ping")
626
+ assert_successful_response(response)
627
+ assert_equal({}, response[:result])
628
+ end
629
+
630
+ def test_echo_tool
631
+ response = @client.call_tool("echo", { "message" => "Hello MCP!" })
632
+ assert_successful_response(response)
633
+ assert_equal "Hello MCP!", response.dig(:result, :content, 0, :text)
634
+ end
635
+
636
+ def test_code_analyzer
637
+ # Create test file
638
+ File.write("test.rb", "def hello\n puts 'Hi'\nend")
639
+
640
+ response = @client.call_tool("code_analyzer", {
641
+ "file_path" => "test.rb",
642
+ "analysis_type" => "complexity"
643
+ })
644
+
645
+ assert_successful_response(response)
646
+ result = response.dig(:result, :content, 0, :text)
647
+ assert_includes result, "Complexity Analysis"
648
+ ensure
649
+ File.delete("test.rb") if File.exist?("test.rb")
650
+ end
651
+
652
+ def test_system_status_resource
653
+ response = @client.read_resource("system/status")
654
+ assert_successful_response(response)
655
+
656
+ content = response.dig(:result, :contents, 0, :text)
657
+ data = JSON.parse(content)
658
+
659
+ assert_equal "complete-mcp-server", data["server"]["name"]
660
+ assert_includes ["healthy", "degraded", "critical"], data["health"]
661
+ end
662
+
663
+ def test_health_check
664
+ response = @client.read_resource("health")
665
+ assert_successful_response(response)
666
+
667
+ content = response.dig(:result, :contents, 0, :text)
668
+ data = JSON.parse(content)
669
+
670
+ assert_equal "healthy", data["status"]
671
+ end
672
+
673
+ def test_completion
674
+ # Test tool parameter completion
675
+ response = @client.complete(
676
+ { type: "ref/tool", name: "code_analyzer" },
677
+ { name: "analysis_type", value: "sec" }
678
+ )
679
+
680
+ assert_successful_response(response)
681
+ values = response.dig(:result, :completion, :values)
682
+ assert_includes values, "security"
683
+ end
684
+
685
+ def test_rate_limiting
686
+ # Make many requests quickly
687
+ 100.times do
688
+ @client.call_tool("echo", { "message" => "test" })
689
+ end
690
+
691
+ # Next request should fail with rate limit
692
+ response = @client.call_tool("echo", { "message" => "test" })
693
+ assert response[:error]
694
+ assert_includes response[:error][:message], "Rate limit"
695
+ end
696
+ end
697
+ ```
698
+
699
+ ## Running the Server
700
+
701
+ 1. Save all files in the appropriate directories
702
+ 2. Run the server:
703
+ ```bash
704
+ ./server.rb
705
+ ```
706
+ 3. Connect with MCP Inspector or any MCP client
707
+ 4. The server supports:
708
+ - All MCP protocol methods including ping
709
+ - Tools with completion
710
+ - Resources with real-time data
711
+ - Prompts for AI interactions
712
+ - Sampling for AI assistance
713
+ - Full logging and metrics
714
+ - Authentication and rate limiting
715
+ - Health monitoring
716
+
717
+ ## Key Features Demonstrated
718
+
719
+ 1. **All Protocol Methods**: Including ping, tools, resources, prompts, completion, sampling
720
+ 2. **Middleware Stack**: Authentication, rate limiting, metrics, logging
721
+ 3. **Lifecycle Hooks**: Server and tool-level hooks
722
+ 4. **Error Handling**: With custom error types and recovery
723
+ 5. **Completions**: Smart autocomplete for parameters
724
+ 6. **Health Monitoring**: Built-in health checks and metrics
725
+ 7. **Testing**: Comprehensive test coverage
726
+ 8. **Production Ready**: Authentication, rate limiting, error handling