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,591 @@
1
+ # Test Helpers API Reference
2
+
3
+ Tsikol provides comprehensive test helpers for testing MCP servers, tools, resources, and prompts.
4
+
5
+ ## Module: Tsikol::TestHelpers
6
+
7
+ ### Including Test Helpers
8
+
9
+ ```ruby
10
+ require 'minitest/autorun'
11
+ require 'tsikol/test_helpers'
12
+
13
+ class MyTest < Minitest::Test
14
+ include Tsikol::TestHelpers::Assertions
15
+
16
+ def setup
17
+ @server = Tsikol::Server.new(name: "test")
18
+ @client = Tsikol::TestHelpers::TestClient.new(@server)
19
+ end
20
+ end
21
+ ```
22
+
23
+ ## Class: Tsikol::TestHelpers::TestClient
24
+
25
+ A test client that simulates MCP protocol interactions without network communication.
26
+
27
+ ### Constructor
28
+
29
+ ```ruby
30
+ client = Tsikol::TestHelpers::TestClient.new(server, options = {})
31
+ ```
32
+
33
+ #### Parameters
34
+
35
+ - `server`: The Tsikol::Server instance to test
36
+ - `options` (Hash): Configuration options
37
+ - `:client_id` - Client identifier for the session
38
+ - `:strict_mode` - Enable strict protocol validation
39
+
40
+ ### Connection Methods
41
+
42
+ #### `#initialize_connection`
43
+
44
+ Initialize the MCP connection and retrieve capabilities.
45
+
46
+ ```ruby
47
+ response = @client.initialize_connection
48
+
49
+ # Response includes server capabilities
50
+ response[:result][:capabilities]
51
+ # => {
52
+ # tools: { listChanged: true },
53
+ # resources: { subscribe: true, listChanged: true },
54
+ # prompts: { listChanged: true },
55
+ # completion: { providers: [...] },
56
+ # sampling: {}
57
+ # }
58
+ ```
59
+
60
+ ### Tool Methods
61
+
62
+ #### `#list_tools`
63
+
64
+ List all available tools.
65
+
66
+ ```ruby
67
+ response = @client.list_tools
68
+
69
+ response[:result][:tools]
70
+ # => [
71
+ # {
72
+ # name: "file_manager",
73
+ # description: "Manage files",
74
+ # inputSchema: { ... }
75
+ # }
76
+ # ]
77
+ ```
78
+
79
+ #### `#call_tool(name, arguments)`
80
+
81
+ Call a tool with arguments.
82
+
83
+ ```ruby
84
+ response = @client.call_tool("calculator", {
85
+ "operation" => "add",
86
+ "a" => 5,
87
+ "b" => 3
88
+ })
89
+
90
+ response[:result][:content]
91
+ # => [{ type: "text", text: "8" }]
92
+ ```
93
+
94
+ ### Resource Methods
95
+
96
+ #### `#list_resources`
97
+
98
+ List all available resources.
99
+
100
+ ```ruby
101
+ response = @client.list_resources
102
+
103
+ response[:result][:resources]
104
+ # => [
105
+ # {
106
+ # uri: "system/status",
107
+ # name: "System Status",
108
+ # description: "Current system status",
109
+ # mimeType: "application/json"
110
+ # }
111
+ # ]
112
+ ```
113
+
114
+ #### `#read_resource(uri)`
115
+
116
+ Read a resource by URI.
117
+
118
+ ```ruby
119
+ response = @client.read_resource("system/status")
120
+
121
+ response[:result][:contents]
122
+ # => [
123
+ # {
124
+ # uri: "system/status",
125
+ # mimeType: "application/json",
126
+ # text: '{"status":"healthy"}'
127
+ # }
128
+ # ]
129
+ ```
130
+
131
+ ### Prompt Methods
132
+
133
+ #### `#list_prompts`
134
+
135
+ List all available prompts.
136
+
137
+ ```ruby
138
+ response = @client.list_prompts
139
+
140
+ response[:result][:prompts]
141
+ # => [
142
+ # {
143
+ # name: "code_assistant",
144
+ # description: "AI code assistant",
145
+ # arguments: [...]
146
+ # }
147
+ # ]
148
+ ```
149
+
150
+ #### `#get_prompt(name, arguments)`
151
+
152
+ Get a prompt with arguments.
153
+
154
+ ```ruby
155
+ response = @client.get_prompt("translator", {
156
+ "text" => "Hello",
157
+ "target_language" => "spanish"
158
+ })
159
+
160
+ response[:result][:messages]
161
+ # => [
162
+ # { role: "system", content: { type: "text", text: "..." } },
163
+ # { role: "user", content: { type: "text", text: "..." } }
164
+ # ]
165
+ ```
166
+
167
+ ### Completion Methods
168
+
169
+ #### `#complete(ref, argument)`
170
+
171
+ Get completion suggestions.
172
+
173
+ ```ruby
174
+ # Tool parameter completion
175
+ response = @client.complete(
176
+ { type: "ref/tool", name: "file_manager" },
177
+ { name: "path", value: "/usr/lo" }
178
+ )
179
+
180
+ response[:result][:completion][:values]
181
+ # => ["/usr/local", "/usr/local/bin", "/usr/local/lib"]
182
+
183
+ # Prompt argument completion
184
+ response = @client.complete(
185
+ { type: "ref/prompt", name: "code_review" },
186
+ { name: "language", value: "jav" }
187
+ )
188
+
189
+ response[:result][:completion][:values]
190
+ # => ["java", "javascript"]
191
+ ```
192
+
193
+ ### Sampling Methods
194
+
195
+ #### `#sample_text(messages, options = {})`
196
+
197
+ Request text generation (if server has sampling enabled).
198
+
199
+ ```ruby
200
+ response = @client.sample_text(
201
+ [
202
+ { role: "user", content: { type: "text", text: "Hello!" } }
203
+ ],
204
+ temperature: 0.7,
205
+ max_tokens: 100
206
+ )
207
+
208
+ response[:result][:text]
209
+ # => "Hello! How can I help you today?"
210
+ ```
211
+
212
+ ### Notification Methods
213
+
214
+ #### `#send_notification(method, params = {})`
215
+
216
+ Send a notification (no response expected).
217
+
218
+ ```ruby
219
+ @client.send_notification("notifications/cancelled", {
220
+ requestId: "123",
221
+ reason: "User cancelled"
222
+ })
223
+ ```
224
+
225
+ ### Logging Methods
226
+
227
+ #### `#send_log(level, message, data = nil)`
228
+
229
+ Send a log message (if server has logging enabled).
230
+
231
+ ```ruby
232
+ @client.send_log(:info, "Test started", { test_name: "example" })
233
+ @client.send_log(:error, "Test failed", { error: "Timeout" })
234
+ ```
235
+
236
+ ## Module: Tsikol::TestHelpers::Assertions
237
+
238
+ Custom assertions for testing MCP responses.
239
+
240
+ ### Response Assertions
241
+
242
+ #### `assert_successful_response(response)`
243
+
244
+ Assert that a response succeeded.
245
+
246
+ ```ruby
247
+ response = @client.call_tool("echo", { "message" => "Hello" })
248
+ assert_successful_response(response)
249
+ ```
250
+
251
+ #### `assert_error_response(response, expected_code = nil)`
252
+
253
+ Assert that a response contains an error.
254
+
255
+ ```ruby
256
+ response = @client.call_tool("invalid_tool", {})
257
+ assert_error_response(response, -32601) # Method not found
258
+ ```
259
+
260
+ #### `assert_response_contains(response, expected)`
261
+
262
+ Assert that response contains expected content.
263
+
264
+ ```ruby
265
+ response = @client.read_resource("config")
266
+ assert_response_contains(response, "database_url")
267
+ ```
268
+
269
+ ### Content Assertions
270
+
271
+ #### `assert_tool_result(response, expected)`
272
+
273
+ Assert tool result matches expected value.
274
+
275
+ ```ruby
276
+ response = @client.call_tool("calculator", {
277
+ "operation" => "multiply",
278
+ "a" => 6,
279
+ "b" => 7
280
+ })
281
+ assert_tool_result(response, "42")
282
+ ```
283
+
284
+ #### `assert_resource_content(response, expected)`
285
+
286
+ Assert resource content matches expected value.
287
+
288
+ ```ruby
289
+ response = @client.read_resource("version")
290
+ assert_resource_content(response, "1.0.0")
291
+ ```
292
+
293
+ #### `assert_prompt_messages(response, expected_count)`
294
+
295
+ Assert prompt returns expected number of messages.
296
+
297
+ ```ruby
298
+ response = @client.get_prompt("assistant", { "task" => "help" })
299
+ assert_prompt_messages(response, 2) # System + user message
300
+ ```
301
+
302
+ ### Validation Assertions
303
+
304
+ #### `assert_valid_tool_schema(tool)`
305
+
306
+ Assert tool has valid parameter schema.
307
+
308
+ ```ruby
309
+ response = @client.list_tools
310
+ tool = response[:result][:tools].first
311
+ assert_valid_tool_schema(tool)
312
+ ```
313
+
314
+ #### `assert_valid_prompt_arguments(prompt)`
315
+
316
+ Assert prompt has valid argument definitions.
317
+
318
+ ```ruby
319
+ response = @client.list_prompts
320
+ prompt = response[:result][:prompts].first
321
+ assert_valid_prompt_arguments(prompt)
322
+ ```
323
+
324
+ ## Test Helpers Utilities
325
+
326
+ ### Mock Server
327
+
328
+ Create a mock server for testing:
329
+
330
+ ```ruby
331
+ class MockServer < Tsikol::TestHelpers::MockServer
332
+ def initialize
333
+ super(name: "mock-server")
334
+
335
+ # Configure mock responses
336
+ mock_tool_response("echo", ->(args) { args["message"] })
337
+ mock_resource_response("time", -> { Time.now.iso8601 })
338
+ mock_prompt_response("simple", ->(args) {
339
+ [{ role: "user", content: { type: "text", text: args["text"] } }]
340
+ })
341
+ end
342
+ end
343
+ ```
344
+
345
+ ### Test Data Generators
346
+
347
+ Generate test data for various scenarios:
348
+
349
+ ```ruby
350
+ module TestData
351
+ extend Tsikol::TestHelpers::Generators
352
+
353
+ # Generate valid JSON-RPC request
354
+ def self.request(method, params = {})
355
+ {
356
+ jsonrpc: "2.0",
357
+ id: random_id,
358
+ method: method,
359
+ params: params
360
+ }
361
+ end
362
+
363
+ # Generate tool parameters
364
+ def self.tool_params
365
+ {
366
+ "string_param" => random_string,
367
+ "number_param" => random_number,
368
+ "boolean_param" => random_boolean,
369
+ "array_param" => random_array,
370
+ "object_param" => random_object
371
+ }
372
+ end
373
+ end
374
+ ```
375
+
376
+ ### Performance Testing
377
+
378
+ Test performance and response times:
379
+
380
+ ```ruby
381
+ class PerformanceTest < Minitest::Test
382
+ include Tsikol::TestHelpers::Performance
383
+
384
+ def test_tool_performance
385
+ assert_response_time(max_ms: 100) do
386
+ @client.call_tool("quick_tool", { "data" => "test" })
387
+ end
388
+ end
389
+
390
+ def test_resource_caching
391
+ # First call
392
+ time1 = measure_time do
393
+ @client.read_resource("expensive/data")
394
+ end
395
+
396
+ # Cached call should be faster
397
+ time2 = measure_time do
398
+ @client.read_resource("expensive/data")
399
+ end
400
+
401
+ assert time2 < time1 / 10, "Cache should improve performance"
402
+ end
403
+
404
+ def test_concurrent_requests
405
+ assert_handles_concurrent_requests(count: 100) do |i|
406
+ @client.call_tool("echo", { "message" => "Request #{i}" })
407
+ end
408
+ end
409
+ end
410
+ ```
411
+
412
+ ## Complete Test Example
413
+
414
+ ```ruby
415
+ require 'minitest/autorun'
416
+ require 'tsikol/test_helpers'
417
+
418
+ class ComprehensiveTest < Minitest::Test
419
+ include Tsikol::TestHelpers::Assertions
420
+
421
+ def setup
422
+ # Create server with all features
423
+ @server = Tsikol::Server.new(name: "test-server")
424
+ @server.logging true
425
+ @server.completion true
426
+ @server.sampling true
427
+
428
+ # Add middleware
429
+ @server.use Tsikol::LoggingMiddleware, level: :debug
430
+ @server.use Tsikol::ErrorHandlingMiddleware
431
+
432
+ # Register components
433
+ register_test_components
434
+
435
+ # Create test client
436
+ @client = Tsikol::TestHelpers::TestClient.new(@server)
437
+ @client.initialize_connection
438
+ end
439
+
440
+ def teardown
441
+ # Cleanup
442
+ cleanup_test_files
443
+ end
444
+
445
+ def test_complete_workflow
446
+ # 1. List available tools
447
+ response = @client.list_tools
448
+ assert_successful_response(response)
449
+
450
+ tools = response[:result][:tools]
451
+ assert tools.any? { |t| t[:name] == "file_manager" }
452
+
453
+ # 2. Create a test file using tool
454
+ response = @client.call_tool("file_manager", {
455
+ "operation" => "write",
456
+ "path" => "test.txt",
457
+ "content" => "Hello, World!"
458
+ })
459
+ assert_successful_response(response)
460
+
461
+ # 3. Read the file back
462
+ response = @client.call_tool("file_manager", {
463
+ "operation" => "read",
464
+ "path" => "test.txt"
465
+ })
466
+ assert_tool_result(response, "Hello, World!")
467
+
468
+ # 4. Check system status resource
469
+ response = @client.read_resource("system/status")
470
+ assert_successful_response(response)
471
+
472
+ status = JSON.parse(response[:result][:contents][0][:text])
473
+ assert_equal "healthy", status["status"]
474
+
475
+ # 5. Get AI prompt
476
+ response = @client.get_prompt("assistant", {
477
+ "task" => "Review the file test.txt"
478
+ })
479
+ assert_successful_response(response)
480
+ assert_prompt_messages(response, 2)
481
+
482
+ # 6. Test completion
483
+ response = @client.complete(
484
+ { type: "ref/tool", name: "file_manager" },
485
+ { name: "operation", value: "rea" }
486
+ )
487
+ assert_successful_response(response)
488
+ assert response[:result][:completion][:values].include?("read")
489
+
490
+ # 7. Test error handling
491
+ response = @client.call_tool("file_manager", {
492
+ "operation" => "read",
493
+ "path" => "nonexistent.txt"
494
+ })
495
+ assert_error_response(response)
496
+ assert_match /not found/i, response[:error][:message]
497
+ end
498
+
499
+ def test_concurrent_operations
500
+ threads = 10.times.map do |i|
501
+ Thread.new do
502
+ # Each thread performs operations
503
+ response = @client.call_tool("echo", {
504
+ "message" => "Thread #{i}"
505
+ })
506
+ assert_successful_response(response)
507
+ assert_tool_result(response, "Thread #{i}")
508
+ end
509
+ end
510
+
511
+ threads.each(&:join)
512
+ end
513
+
514
+ private
515
+
516
+ def register_test_components
517
+ # Register test tool
518
+ @server.tool "echo" do |message:|
519
+ message
520
+ end
521
+
522
+ # Register file manager tool
523
+ @server.register_tool_instance(TestFileManager.new)
524
+
525
+ # Register test resource
526
+ @server.resource "system/status" do
527
+ { status: "healthy", timestamp: Time.now.iso8601 }.to_json
528
+ end
529
+
530
+ # Register test prompt
531
+ @server.prompt "assistant" do |task:|
532
+ [
533
+ { role: "system", content: { type: "text", text: "You are helpful." } },
534
+ { role: "user", content: { type: "text", text: task } }
535
+ ]
536
+ end
537
+ end
538
+
539
+ def cleanup_test_files
540
+ File.delete("test.txt") if File.exist?("test.txt")
541
+ end
542
+ end
543
+
544
+ class TestFileManager < Tsikol::Tool
545
+ description "Test file manager"
546
+
547
+ parameter :operation do
548
+ type :string
549
+ required
550
+ enum ["read", "write"]
551
+ end
552
+
553
+ parameter :path do
554
+ type :string
555
+ required
556
+ end
557
+
558
+ parameter :content do
559
+ type :string
560
+ optional
561
+ end
562
+
563
+ def execute(operation:, path:, content: nil)
564
+ case operation
565
+ when "read"
566
+ raise Tsikol::ValidationError, "File not found" unless File.exist?(path)
567
+ File.read(path)
568
+ when "write"
569
+ File.write(path, content)
570
+ "File written successfully"
571
+ end
572
+ end
573
+ end
574
+ ```
575
+
576
+ ## Testing Best Practices
577
+
578
+ 1. **Isolation**: Test components in isolation before integration
579
+ 2. **Coverage**: Test success cases, error cases, and edge cases
580
+ 3. **Assertions**: Use specific assertions for better error messages
581
+ 4. **Cleanup**: Always clean up test data and resources
582
+ 5. **Concurrency**: Test thread safety with concurrent requests
583
+ 6. **Performance**: Set reasonable performance expectations
584
+ 7. **Mocking**: Mock external dependencies for reliable tests
585
+
586
+ ## See Also
587
+
588
+ - [Testing Guide](../guides/testing.md)
589
+ - [Server API](server.md)
590
+ - [Tool API](tool.md)
591
+ - [Error Handling](../cookbook/error-handling.md)