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,501 @@
1
+ # Echo Server Example
2
+
3
+ A simple MCP server that echoes back input with various transformations.
4
+
5
+ ## Overview
6
+
7
+ This example demonstrates:
8
+ - Basic tool implementation
9
+ - Parameter validation
10
+ - Multiple operations in a single tool
11
+ - Error handling
12
+ - Logging
13
+
14
+ ## Implementation
15
+
16
+ ### server.rb
17
+
18
+ ```ruby
19
+ #!/usr/bin/env ruby
20
+
21
+ require 'tsikol'
22
+
23
+ # Simple echo tool with transformations
24
+ class EchoTool < Tsikol::Tool
25
+ name "echo"
26
+ description "Echoes text with optional transformations"
27
+
28
+ parameter :text do
29
+ type :string
30
+ required
31
+ description "Text to echo"
32
+ end
33
+
34
+ parameter :transform do
35
+ type :string
36
+ optional
37
+ default "none"
38
+ enum ["none", "uppercase", "lowercase", "reverse", "capitalize"]
39
+ description "Transformation to apply"
40
+ end
41
+
42
+ parameter :repeat do
43
+ type :number
44
+ optional
45
+ default 1
46
+ description "Number of times to repeat"
47
+ end
48
+
49
+ def execute(text:, transform: "none", repeat: 1)
50
+ # Validate repeat count
51
+ if repeat < 1 || repeat > 10
52
+ raise ArgumentError, "Repeat must be between 1 and 10"
53
+ end
54
+
55
+ # Apply transformation
56
+ result = case transform
57
+ when "uppercase"
58
+ text.upcase
59
+ when "lowercase"
60
+ text.downcase
61
+ when "reverse"
62
+ text.reverse
63
+ when "capitalize"
64
+ text.split.map(&:capitalize).join(" ")
65
+ else
66
+ text
67
+ end
68
+
69
+ # Repeat if requested
70
+ if repeat > 1
71
+ result = Array.new(repeat, result).join(" ")
72
+ end
73
+
74
+ log :info, "Echoed text",
75
+ original_length: text.length,
76
+ transform: transform,
77
+ repeat: repeat
78
+
79
+ result
80
+ end
81
+ end
82
+
83
+ # ASCII art generator tool
84
+ class AsciiArtTool < Tsikol::Tool
85
+ name "ascii_art"
86
+ description "Generate simple ASCII art"
87
+
88
+ parameter :type do
89
+ type :string
90
+ required
91
+ enum ["box", "banner", "divider"]
92
+ description "Type of ASCII art to generate"
93
+ end
94
+
95
+ parameter :text do
96
+ type :string
97
+ optional
98
+ description "Text to include in the art"
99
+ end
100
+
101
+ parameter :width do
102
+ type :number
103
+ optional
104
+ default 40
105
+ description "Width of the art"
106
+ end
107
+
108
+ def execute(type:, text: nil, width: 40)
109
+ case type
110
+ when "box"
111
+ create_box(text || "Hello", width)
112
+ when "banner"
113
+ create_banner(text || "BANNER", width)
114
+ when "divider"
115
+ create_divider(width)
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ def create_box(text, width)
122
+ # Ensure text fits
123
+ text = text[0...width-4] if text.length > width - 4
124
+
125
+ border = "+" + "-" * (width - 2) + "+"
126
+ padding = (width - 4 - text.length) / 2
127
+ content = "| " + " " * padding + text + " " * (width - 4 - padding - text.length) + " |"
128
+
129
+ [border, content, border].join("\n")
130
+ end
131
+
132
+ def create_banner(text, width)
133
+ text = text.upcase[0...width]
134
+ "=" * width + "\n" + text.center(width) + "\n" + "=" * width
135
+ end
136
+
137
+ def create_divider(width)
138
+ "-" * width
139
+ end
140
+ end
141
+
142
+ # Start the server
143
+ Tsikol.start(
144
+ name: "echo-server",
145
+ version: "1.0.0",
146
+ description: "Simple echo server with text transformations"
147
+ ) do
148
+ # Enable logging
149
+ logging true
150
+
151
+ # Use logging middleware
152
+ use Tsikol::LoggingMiddleware, level: :info
153
+
154
+ # Register tools
155
+ tool EchoTool
156
+ tool AsciiArtTool
157
+
158
+ # Add a simple resource
159
+ resource "status" do
160
+ description "Server status information"
161
+
162
+ def read
163
+ {
164
+ status: "operational",
165
+ uptime: Time.now - @started_at,
166
+ tools_available: ["echo", "ascii_art"],
167
+ requests_served: @request_count || 0
168
+ }.to_json
169
+ end
170
+ end
171
+
172
+ # Track server start time
173
+ before_start do
174
+ @started_at = Time.now
175
+ @request_count = 0
176
+ end
177
+
178
+ # Count requests
179
+ after_request do |request, response|
180
+ @request_count += 1
181
+ end
182
+ end
183
+ ```
184
+
185
+ ### Gemfile
186
+
187
+ ```ruby
188
+ source 'https://rubygems.org'
189
+
190
+ gem 'tsikol', '~> 1.0'
191
+ ```
192
+
193
+ ## Usage
194
+
195
+ ### Starting the Server
196
+
197
+ ```bash
198
+ bundle install
199
+ ruby server.rb
200
+ ```
201
+
202
+ ### Example Requests
203
+
204
+ #### Basic Echo
205
+
206
+ ```json
207
+ {
208
+ "jsonrpc": "2.0",
209
+ "id": 1,
210
+ "method": "tools/call",
211
+ "params": {
212
+ "name": "echo",
213
+ "arguments": {
214
+ "text": "Hello, World!"
215
+ }
216
+ }
217
+ }
218
+ ```
219
+
220
+ Response:
221
+ ```json
222
+ {
223
+ "jsonrpc": "2.0",
224
+ "id": 1,
225
+ "result": {
226
+ "content": [
227
+ {
228
+ "type": "text",
229
+ "text": "Hello, World!"
230
+ }
231
+ ]
232
+ }
233
+ }
234
+ ```
235
+
236
+ #### Echo with Transformation
237
+
238
+ ```json
239
+ {
240
+ "jsonrpc": "2.0",
241
+ "id": 2,
242
+ "method": "tools/call",
243
+ "params": {
244
+ "name": "echo",
245
+ "arguments": {
246
+ "text": "Hello, World!",
247
+ "transform": "uppercase",
248
+ "repeat": 3
249
+ }
250
+ }
251
+ }
252
+ ```
253
+
254
+ Response:
255
+ ```json
256
+ {
257
+ "jsonrpc": "2.0",
258
+ "id": 2,
259
+ "result": {
260
+ "content": [
261
+ {
262
+ "type": "text",
263
+ "text": "HELLO, WORLD! HELLO, WORLD! HELLO, WORLD!"
264
+ }
265
+ ]
266
+ }
267
+ }
268
+ ```
269
+
270
+ #### ASCII Art Generation
271
+
272
+ ```json
273
+ {
274
+ "jsonrpc": "2.0",
275
+ "id": 3,
276
+ "method": "tools/call",
277
+ "params": {
278
+ "name": "ascii_art",
279
+ "arguments": {
280
+ "type": "box",
281
+ "text": "Welcome",
282
+ "width": 30
283
+ }
284
+ }
285
+ }
286
+ ```
287
+
288
+ Response:
289
+ ```json
290
+ {
291
+ "jsonrpc": "2.0",
292
+ "id": 3,
293
+ "result": {
294
+ "content": [
295
+ {
296
+ "type": "text",
297
+ "text": "+----------------------------+\n| Welcome |\n+----------------------------+"
298
+ }
299
+ ]
300
+ }
301
+ }
302
+ ```
303
+
304
+ #### Server Status
305
+
306
+ ```json
307
+ {
308
+ "jsonrpc": "2.0",
309
+ "id": 4,
310
+ "method": "resources/read",
311
+ "params": {
312
+ "uri": "status"
313
+ }
314
+ }
315
+ ```
316
+
317
+ ## Testing
318
+
319
+ ### test_echo_server.rb
320
+
321
+ ```ruby
322
+ require 'minitest/autorun'
323
+ require 'tsikol/test_helpers'
324
+ require_relative 'server'
325
+
326
+ class EchoServerTest < Minitest::Test
327
+ include Tsikol::TestHelpers
328
+
329
+ def setup
330
+ @server = create_test_server
331
+ @client = TestClient.new(@server)
332
+ end
333
+
334
+ def test_basic_echo
335
+ response = @client.call_tool("echo", {
336
+ "text" => "Hello, Test!"
337
+ })
338
+
339
+ assert_successful_response(response)
340
+ assert_equal "Hello, Test!", response.dig(:result, :content, 0, :text)
341
+ end
342
+
343
+ def test_echo_with_uppercase
344
+ response = @client.call_tool("echo", {
345
+ "text" => "hello",
346
+ "transform" => "uppercase"
347
+ })
348
+
349
+ assert_successful_response(response)
350
+ assert_equal "HELLO", response.dig(:result, :content, 0, :text)
351
+ end
352
+
353
+ def test_echo_with_repeat
354
+ response = @client.call_tool("echo", {
355
+ "text" => "Hi",
356
+ "repeat" => 3
357
+ })
358
+
359
+ assert_successful_response(response)
360
+ assert_equal "Hi Hi Hi", response.dig(:result, :content, 0, :text)
361
+ end
362
+
363
+ def test_invalid_repeat_count
364
+ response = @client.call_tool("echo", {
365
+ "text" => "Test",
366
+ "repeat" => 20
367
+ })
368
+
369
+ assert_error_response(response)
370
+ assert_match /between 1 and 10/, response[:error][:message]
371
+ end
372
+
373
+ def test_ascii_art_box
374
+ response = @client.call_tool("ascii_art", {
375
+ "type" => "box",
376
+ "text" => "Test",
377
+ "width" => 20
378
+ })
379
+
380
+ assert_successful_response(response)
381
+ result = response.dig(:result, :content, 0, :text)
382
+ assert_match /\+\-+\+/, result
383
+ assert_match /Test/, result
384
+ end
385
+
386
+ def test_server_status
387
+ response = @client.read_resource("status")
388
+
389
+ assert_successful_response(response)
390
+
391
+ status = JSON.parse(response.dig(:result, :contents, 0, :text))
392
+ assert_equal "operational", status["status"]
393
+ assert_includes status["tools_available"], "echo"
394
+ assert_includes status["tools_available"], "ascii_art"
395
+ end
396
+
397
+ private
398
+
399
+ def create_test_server
400
+ Tsikol::Server.new(name: "test-echo-server") do
401
+ tool EchoTool
402
+ tool AsciiArtTool
403
+
404
+ resource "status" do
405
+ def read
406
+ { status: "operational" }.to_json
407
+ end
408
+ end
409
+ end
410
+ end
411
+ end
412
+ ```
413
+
414
+ Run tests:
415
+ ```bash
416
+ ruby test_echo_server.rb
417
+ ```
418
+
419
+ ## Extensions
420
+
421
+ ### Adding More Transformations
422
+
423
+ ```ruby
424
+ parameter :transform do
425
+ type :string
426
+ optional
427
+ default "none"
428
+ enum ["none", "uppercase", "lowercase", "reverse", "capitalize",
429
+ "rot13", "leetspeak", "titlecase", "snakecase", "camelcase"]
430
+ description "Transformation to apply"
431
+ end
432
+
433
+ def execute(text:, transform: "none", repeat: 1)
434
+ result = case transform
435
+ when "rot13"
436
+ text.tr('A-Za-z', 'N-ZA-Mn-za-m')
437
+ when "leetspeak"
438
+ text.gsub(/[aeioAEIO]/, 'a' => '4', 'e' => '3', 'i' => '1', 'o' => '0',
439
+ 'A' => '4', 'E' => '3', 'I' => '1', 'O' => '0')
440
+ when "titlecase"
441
+ text.split.map { |word| word[0].upcase + word[1..-1].downcase }.join(" ")
442
+ when "snakecase"
443
+ text.downcase.gsub(/\s+/, '_')
444
+ when "camelcase"
445
+ text.split.map.with_index { |word, i|
446
+ i == 0 ? word.downcase : word.capitalize
447
+ }.join
448
+ # ... existing cases
449
+ end
450
+
451
+ # ... rest of method
452
+ end
453
+ ```
454
+
455
+ ### Adding Word Statistics
456
+
457
+ ```ruby
458
+ class WordStatsTool < Tsikol::Tool
459
+ name "word_stats"
460
+ description "Analyze text and provide statistics"
461
+
462
+ parameter :text do
463
+ type :string
464
+ required
465
+ description "Text to analyze"
466
+ end
467
+
468
+ def execute(text:)
469
+ words = text.split(/\s+/)
470
+
471
+ {
472
+ character_count: text.length,
473
+ word_count: words.length,
474
+ line_count: text.lines.count,
475
+ average_word_length: words.map(&:length).sum.to_f / words.length,
476
+ longest_word: words.max_by(&:length),
477
+ most_common_word: words.group_by(&:itself)
478
+ .max_by { |_, v| v.length }
479
+ &.first
480
+ }.to_json
481
+ end
482
+ end
483
+ ```
484
+
485
+ ## Best Practices Demonstrated
486
+
487
+ 1. **Parameter Validation** - Check bounds and provide clear errors
488
+ 2. **Comprehensive Logging** - Log operations for debugging
489
+ 3. **Error Handling** - Graceful handling of invalid inputs
490
+ 4. **Testing** - Full test coverage including edge cases
491
+ 5. **Documentation** - Clear examples and usage instructions
492
+ 6. **Extensibility** - Easy to add new transformations
493
+ 7. **Resource Monitoring** - Status endpoint for health checks
494
+
495
+ ## Next Steps
496
+
497
+ - Add more text transformation options
498
+ - Implement caching for repeated requests
499
+ - Add rate limiting for production use
500
+ - Create a web UI for interactive testing
501
+ - Add persistent storage for echo history