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,1382 @@
1
+ # DevOps Tools Example
2
+
3
+ A comprehensive MCP server providing DevOps automation tools including Docker management, Kubernetes operations, CI/CD pipelines, and infrastructure monitoring.
4
+
5
+ ## Overview
6
+
7
+ This example demonstrates:
8
+ - Docker container management
9
+ - Kubernetes deployment operations
10
+ - CI/CD pipeline execution
11
+ - Infrastructure monitoring
12
+ - Log aggregation and analysis
13
+ - Configuration management
14
+ - Deployment automation
15
+
16
+ ## Implementation
17
+
18
+ ### server.rb
19
+
20
+ ```ruby
21
+ #!/usr/bin/env ruby
22
+
23
+ require 'tsikol'
24
+ require 'docker-api'
25
+ require 'kubeclient'
26
+ require 'net/ssh'
27
+ require 'yaml'
28
+
29
+ # Docker operations tool
30
+ class DockerTool < Tsikol::Tool
31
+ name "docker"
32
+ description "Manage Docker containers and images"
33
+
34
+ parameter :operation do
35
+ type :string
36
+ required
37
+ enum ["list", "run", "stop", "remove", "logs", "inspect", "build"]
38
+ description "Docker operation to perform"
39
+ end
40
+
41
+ parameter :options do
42
+ type :object
43
+ optional
44
+ default {}
45
+ description "Operation-specific options"
46
+ end
47
+
48
+ def execute(operation:, options: {})
49
+ case operation
50
+ when "list"
51
+ list_containers(options)
52
+ when "run"
53
+ run_container(options)
54
+ when "stop"
55
+ stop_container(options)
56
+ when "remove"
57
+ remove_container(options)
58
+ when "logs"
59
+ get_logs(options)
60
+ when "inspect"
61
+ inspect_container(options)
62
+ when "build"
63
+ build_image(options)
64
+ else
65
+ "Unknown operation: #{operation}"
66
+ end
67
+ rescue Docker::Error::NotFoundError => e
68
+ "Docker error: #{e.message}"
69
+ rescue Excon::Error::Socket => e
70
+ "Cannot connect to Docker daemon. Is it running?"
71
+ end
72
+
73
+ private
74
+
75
+ def list_containers(options)
76
+ all = options["all"] || false
77
+ containers = Docker::Container.all(all: all)
78
+
79
+ if containers.empty?
80
+ return "No containers found"
81
+ end
82
+
83
+ lines = ["CONTAINER ID IMAGE STATUS NAMES"]
84
+ lines << "-" * 70
85
+
86
+ containers.each do |container|
87
+ info = container.info
88
+ id = info["Id"][0..11]
89
+ image = info["Image"]
90
+ status = info["Status"]
91
+ names = info["Names"].join(", ").gsub(/^\//, "")
92
+
93
+ lines << "%-15s %-20s %-15s %s" % [id, image[0..19], status[0..14], names]
94
+ end
95
+
96
+ lines.join("\n")
97
+ end
98
+
99
+ def run_container(options)
100
+ image = options["image"] || raise("Image name required")
101
+ name = options["name"]
102
+ ports = options["ports"] || {}
103
+ env = options["env"] || []
104
+ volumes = options["volumes"] || {}
105
+ command = options["command"]
106
+
107
+ config = {
108
+ 'Image' => image,
109
+ 'Env' => env
110
+ }
111
+
112
+ config['Cmd'] = command.split if command
113
+
114
+ host_config = {
115
+ 'PortBindings' => {},
116
+ 'Binds' => []
117
+ }
118
+
119
+ # Configure port mappings
120
+ ports.each do |container_port, host_port|
121
+ host_config['PortBindings']["#{container_port}/tcp"] = [
122
+ { 'HostPort' => host_port.to_s }
123
+ ]
124
+ end
125
+
126
+ # Configure volume mounts
127
+ volumes.each do |host_path, container_path|
128
+ host_config['Binds'] << "#{host_path}:#{container_path}"
129
+ end
130
+
131
+ config['HostConfig'] = host_config
132
+ config['name'] = name if name
133
+
134
+ container = Docker::Container.create(config)
135
+ container.start
136
+
137
+ "Container started: #{container.id[0..11]} (#{image})"
138
+ end
139
+
140
+ def stop_container(options)
141
+ id = options["id"] || raise("Container ID required")
142
+ timeout = options["timeout"] || 10
143
+
144
+ container = Docker::Container.get(id)
145
+ container.stop(t: timeout)
146
+
147
+ "Container stopped: #{id}"
148
+ end
149
+
150
+ def get_logs(options)
151
+ id = options["id"] || raise("Container ID required")
152
+ lines = options["lines"] || 100
153
+ follow = options["follow"] || false
154
+
155
+ container = Docker::Container.get(id)
156
+
157
+ logs = container.logs(
158
+ stdout: true,
159
+ stderr: true,
160
+ tail: lines
161
+ )
162
+
163
+ logs
164
+ end
165
+ end
166
+
167
+ # Kubernetes operations tool
168
+ class KubernetesTool < Tsikol::Tool
169
+ name "kubernetes"
170
+ description "Manage Kubernetes resources"
171
+
172
+ parameter :operation do
173
+ type :string
174
+ required
175
+ enum ["get", "create", "apply", "delete", "scale", "rollout", "logs"]
176
+ description "Kubernetes operation"
177
+ end
178
+
179
+ parameter :resource_type do
180
+ type :string
181
+ required
182
+ enum ["pod", "deployment", "service", "configmap", "secret", "ingress"]
183
+ description "Type of Kubernetes resource"
184
+ end
185
+
186
+ parameter :options do
187
+ type :object
188
+ optional
189
+ default {}
190
+ description "Operation-specific options"
191
+ end
192
+
193
+ def execute(operation:, resource_type:, options: {})
194
+ client = create_k8s_client(resource_type)
195
+
196
+ case operation
197
+ when "get"
198
+ get_resources(client, resource_type, options)
199
+ when "create", "apply"
200
+ apply_resource(client, resource_type, options)
201
+ when "delete"
202
+ delete_resource(client, resource_type, options)
203
+ when "scale"
204
+ scale_resource(client, resource_type, options)
205
+ when "rollout"
206
+ manage_rollout(client, resource_type, options)
207
+ when "logs"
208
+ get_pod_logs(resource_type, options)
209
+ end
210
+ rescue Kubeclient::HttpError => e
211
+ "Kubernetes error: #{JSON.parse(e.response)['message']}"
212
+ rescue => e
213
+ "Error: #{e.message}"
214
+ end
215
+
216
+ private
217
+
218
+ def create_k8s_client(resource_type)
219
+ config = Kubeclient::Config.read(ENV['KUBECONFIG'] || "#{ENV['HOME']}/.kube/config")
220
+ context = config.context
221
+
222
+ endpoint = context.api_endpoint
223
+
224
+ case resource_type
225
+ when "deployment", "replicaset"
226
+ Kubeclient::Client.new(
227
+ endpoint + '/apis/apps',
228
+ 'v1',
229
+ ssl_options: context.ssl_options,
230
+ auth_options: context.auth_options
231
+ )
232
+ else
233
+ Kubeclient::Client.new(
234
+ endpoint,
235
+ 'v1',
236
+ ssl_options: context.ssl_options,
237
+ auth_options: context.auth_options
238
+ )
239
+ end
240
+ end
241
+
242
+ def get_resources(client, resource_type, options)
243
+ namespace = options["namespace"] || "default"
244
+ name = options["name"]
245
+
246
+ if name
247
+ resource = client.send("get_#{resource_type}", name, namespace)
248
+ format_resource(resource_type, [resource])
249
+ else
250
+ resources = client.send("get_#{resource_type}s", namespace: namespace)
251
+ format_resource(resource_type, resources)
252
+ end
253
+ end
254
+
255
+ def format_resource(type, resources)
256
+ if resources.empty?
257
+ return "No #{type}s found"
258
+ end
259
+
260
+ case type
261
+ when "pod"
262
+ format_pods(resources)
263
+ when "deployment"
264
+ format_deployments(resources)
265
+ when "service"
266
+ format_services(resources)
267
+ else
268
+ # Generic formatting
269
+ resources.map { |r| "#{r.metadata.name} (#{r.metadata.namespace})" }.join("\n")
270
+ end
271
+ end
272
+
273
+ def format_pods(pods)
274
+ lines = ["NAME READY STATUS RESTARTS AGE"]
275
+ lines << "-" * 70
276
+
277
+ pods.each do |pod|
278
+ name = pod.metadata.name
279
+ ready = "#{pod.status.containerStatuses&.count { |c| c.ready }}/#{pod.spec.containers.count}"
280
+ status = pod.status.phase
281
+ restarts = pod.status.containerStatuses&.sum { |c| c.restartCount } || 0
282
+ age = format_age(pod.metadata.creationTimestamp)
283
+
284
+ lines << "%-30s %-7s %-9s %-10s %s" % [name[0..29], ready, status, restarts, age]
285
+ end
286
+
287
+ lines.join("\n")
288
+ end
289
+
290
+ def format_age(timestamp)
291
+ seconds = Time.now - Time.parse(timestamp)
292
+
293
+ if seconds < 60
294
+ "#{seconds.to_i}s"
295
+ elsif seconds < 3600
296
+ "#{(seconds / 60).to_i}m"
297
+ elsif seconds < 86400
298
+ "#{(seconds / 3600).to_i}h"
299
+ else
300
+ "#{(seconds / 86400).to_i}d"
301
+ end
302
+ end
303
+ end
304
+
305
+ # CI/CD Pipeline tool
306
+ class PipelineTool < Tsikol::Tool
307
+ name "pipeline"
308
+ description "Execute CI/CD pipeline stages"
309
+
310
+ parameter :action do
311
+ type :string
312
+ required
313
+ enum ["run", "status", "logs", "retry", "cancel"]
314
+ description "Pipeline action"
315
+ end
316
+
317
+ parameter :pipeline_file do
318
+ type :string
319
+ optional
320
+ default ".pipeline.yml"
321
+ description "Path to pipeline configuration"
322
+
323
+ complete do |partial|
324
+ Dir.glob("#{partial}*.yml") + Dir.glob("#{partial}*.yaml")
325
+ end
326
+ end
327
+
328
+ parameter :stage do
329
+ type :string
330
+ optional
331
+ description "Specific stage to run"
332
+ end
333
+
334
+ parameter :env do
335
+ type :object
336
+ optional
337
+ default {}
338
+ description "Environment variables"
339
+ end
340
+
341
+ def execute(action:, pipeline_file: ".pipeline.yml", stage: nil, env: {})
342
+ unless File.exist?(pipeline_file)
343
+ return "Pipeline file not found: #{pipeline_file}"
344
+ end
345
+
346
+ pipeline = YAML.load_file(pipeline_file)
347
+
348
+ case action
349
+ when "run"
350
+ run_pipeline(pipeline, stage, env)
351
+ when "status"
352
+ pipeline_status(pipeline)
353
+ when "logs"
354
+ show_logs(pipeline, stage)
355
+ else
356
+ "Action not implemented: #{action}"
357
+ end
358
+ end
359
+
360
+ private
361
+
362
+ def run_pipeline(pipeline, specific_stage, env)
363
+ stages = specific_stage ? [pipeline["stages"].find { |s| s["name"] == specific_stage }] : pipeline["stages"]
364
+
365
+ results = []
366
+
367
+ stages.compact.each do |stage|
368
+ log :info, "Running stage", name: stage["name"]
369
+
370
+ result = run_stage(stage, env.merge(pipeline["env"] || {}))
371
+ results << "#{stage['name']}: #{result[:status]}"
372
+
373
+ if result[:status] == "failed" && !stage["allow_failure"]
374
+ results << "Pipeline failed at stage: #{stage['name']}"
375
+ break
376
+ end
377
+ end
378
+
379
+ results.join("\n")
380
+ end
381
+
382
+ def run_stage(stage, env)
383
+ start_time = Time.now
384
+
385
+ # Run pre-scripts
386
+ if stage["before_script"]
387
+ run_scripts(stage["before_script"], env)
388
+ end
389
+
390
+ # Run main scripts
391
+ success = run_scripts(stage["script"], env)
392
+
393
+ # Run post-scripts
394
+ if stage["after_script"]
395
+ run_scripts(stage["after_script"], env)
396
+ end
397
+
398
+ {
399
+ status: success ? "passed" : "failed",
400
+ duration: Time.now - start_time
401
+ }
402
+ end
403
+
404
+ def run_scripts(scripts, env)
405
+ scripts = [scripts] unless scripts.is_a?(Array)
406
+
407
+ scripts.all? do |script|
408
+ log :debug, "Executing", script: script
409
+
410
+ system(env, script)
411
+ end
412
+ end
413
+ end
414
+
415
+ # Server monitoring resource
416
+ class MonitoringResource < Tsikol::Resource
417
+ uri "monitoring/:target/:metric?"
418
+ description "System and application monitoring metrics"
419
+
420
+ def read
421
+ target = params[:target]
422
+ metric = params[:metric]
423
+
424
+ case target
425
+ when "system"
426
+ system_metrics(metric)
427
+ when "docker"
428
+ docker_metrics(metric)
429
+ when "kubernetes"
430
+ kubernetes_metrics(metric)
431
+ when "applications"
432
+ application_metrics(metric)
433
+ else
434
+ { error: "Unknown monitoring target: #{target}" }.to_json
435
+ end
436
+ end
437
+
438
+ private
439
+
440
+ def system_metrics(metric)
441
+ metrics = {
442
+ timestamp: Time.now.iso8601,
443
+ hostname: `hostname`.strip
444
+ }
445
+
446
+ if metric.nil? || metric == "cpu"
447
+ metrics[:cpu] = {
448
+ load_average: `uptime`.match(/load average: (.+)$/)[1].split(", ").map(&:to_f),
449
+ usage_percent: get_cpu_usage
450
+ }
451
+ end
452
+
453
+ if metric.nil? || metric == "memory"
454
+ metrics[:memory] = get_memory_info
455
+ end
456
+
457
+ if metric.nil? || metric == "disk"
458
+ metrics[:disk] = get_disk_usage
459
+ end
460
+
461
+ if metric.nil? || metric == "network"
462
+ metrics[:network] = get_network_stats
463
+ end
464
+
465
+ metrics.to_json
466
+ end
467
+
468
+ def get_cpu_usage
469
+ # Read from /proc/stat on Linux
470
+ if File.exist?("/proc/stat")
471
+ stat1 = File.read("/proc/stat").lines.first.split
472
+ sleep(0.1)
473
+ stat2 = File.read("/proc/stat").lines.first.split
474
+
475
+ # Calculate CPU usage
476
+ prev_idle = stat1[4].to_i
477
+ prev_total = stat1[1..8].map(&:to_i).sum
478
+
479
+ idle = stat2[4].to_i
480
+ total = stat2[1..8].map(&:to_i).sum
481
+
482
+ diff_idle = idle - prev_idle
483
+ diff_total = total - prev_total
484
+
485
+ usage = 100.0 * (1.0 - diff_idle.to_f / diff_total)
486
+ usage.round(2)
487
+ else
488
+ # Fallback for macOS
489
+ `top -l 1 | grep "CPU usage" | awk '{print $3}'`.to_f
490
+ end
491
+ end
492
+
493
+ def get_memory_info
494
+ if File.exist?("/proc/meminfo")
495
+ # Linux
496
+ meminfo = File.read("/proc/meminfo")
497
+ total = meminfo.match(/MemTotal:\s+(\d+)/)[1].to_i
498
+ available = meminfo.match(/MemAvailable:\s+(\d+)/)[1].to_i
499
+
500
+ {
501
+ total_mb: total / 1024,
502
+ available_mb: available / 1024,
503
+ used_mb: (total - available) / 1024,
504
+ usage_percent: ((total - available).to_f / total * 100).round(2)
505
+ }
506
+ else
507
+ # macOS fallback
508
+ vm_stat = `vm_stat`.lines
509
+ page_size = vm_stat[0].match(/page size of (\d+) bytes/)[1].to_i
510
+
511
+ stats = {}
512
+ vm_stat[1..-1].each do |line|
513
+ if match = line.match(/(.+):\s+(\d+)/)
514
+ stats[match[1]] = match[2].to_i * page_size / 1024 / 1024
515
+ end
516
+ end
517
+
518
+ total = `sysctl -n hw.memsize`.to_i / 1024 / 1024
519
+ used = stats["Pages active"] + stats["Pages wired down"]
520
+
521
+ {
522
+ total_mb: total,
523
+ used_mb: used,
524
+ available_mb: total - used,
525
+ usage_percent: (used.to_f / total * 100).round(2)
526
+ }
527
+ end
528
+ end
529
+ end
530
+
531
+ # Deployment automation tool
532
+ class DeploymentTool < Tsikol::Tool
533
+ name "deploy"
534
+ description "Automated deployment operations"
535
+
536
+ parameter :environment do
537
+ type :string
538
+ required
539
+ enum ["development", "staging", "production"]
540
+ description "Target environment"
541
+ end
542
+
543
+ parameter :service do
544
+ type :string
545
+ required
546
+ description "Service to deploy"
547
+ end
548
+
549
+ parameter :version do
550
+ type :string
551
+ required
552
+ description "Version to deploy"
553
+ end
554
+
555
+ parameter :strategy do
556
+ type :string
557
+ optional
558
+ default "rolling"
559
+ enum ["rolling", "blue-green", "canary"]
560
+ description "Deployment strategy"
561
+ end
562
+
563
+ parameter :config do
564
+ type :object
565
+ optional
566
+ default {}
567
+ description "Deployment configuration"
568
+ end
569
+
570
+ def execute(environment:, service:, version:, strategy: "rolling", config: {})
571
+ log :info, "Starting deployment",
572
+ environment: environment,
573
+ service: service,
574
+ version: version,
575
+ strategy: strategy
576
+
577
+ # Load deployment configuration
578
+ deploy_config = load_deploy_config(environment, service)
579
+ deploy_config.merge!(config)
580
+
581
+ # Execute deployment strategy
582
+ case strategy
583
+ when "rolling"
584
+ rolling_deployment(deploy_config, version)
585
+ when "blue-green"
586
+ blue_green_deployment(deploy_config, version)
587
+ when "canary"
588
+ canary_deployment(deploy_config, version)
589
+ end
590
+ end
591
+
592
+ private
593
+
594
+ def load_deploy_config(environment, service)
595
+ config_file = "deploy/#{environment}/#{service}.yml"
596
+
597
+ if File.exist?(config_file)
598
+ YAML.load_file(config_file)
599
+ else
600
+ # Default configuration
601
+ {
602
+ "replicas" => 3,
603
+ "health_check" => "/health",
604
+ "timeout" => 300
605
+ }
606
+ end
607
+ end
608
+
609
+ def rolling_deployment(config, version)
610
+ steps = []
611
+
612
+ steps << "1. Building new version: #{version}"
613
+ # Simulate build
614
+ sleep(2)
615
+
616
+ steps << "2. Pushing to registry"
617
+ # Simulate push
618
+ sleep(1)
619
+
620
+ steps << "3. Updating Kubernetes deployment"
621
+ # Would actually update k8s deployment
622
+
623
+ steps << "4. Waiting for rollout to complete"
624
+ # Monitor rollout status
625
+
626
+ steps << "5. Running health checks"
627
+ # Verify deployment health
628
+
629
+ steps << "\nDeployment completed successfully!"
630
+
631
+ steps.join("\n")
632
+ end
633
+ end
634
+
635
+ # Log aggregation resource
636
+ class LogAggregatorResource < Tsikol::Resource
637
+ uri "logs/:source/:filter?"
638
+ description "Aggregate and search logs from multiple sources"
639
+
640
+ def read
641
+ source = params[:source]
642
+ filter = params[:filter]
643
+ options = parse_query_params
644
+
645
+ case source
646
+ when "application"
647
+ search_application_logs(filter, options)
648
+ when "system"
649
+ search_system_logs(filter, options)
650
+ when "container"
651
+ search_container_logs(filter, options)
652
+ else
653
+ { error: "Unknown log source: #{source}" }.to_json
654
+ end
655
+ end
656
+
657
+ private
658
+
659
+ def search_application_logs(filter, options)
660
+ # In production, this would query a log aggregation service
661
+ logs = []
662
+
663
+ # Simulate log entries
664
+ 10.times do |i|
665
+ logs << {
666
+ timestamp: (Time.now - i * 60).iso8601,
667
+ level: ["INFO", "WARN", "ERROR"].sample,
668
+ service: ["api", "web", "worker"].sample,
669
+ message: "Sample log message #{i}",
670
+ metadata: {
671
+ request_id: SecureRandom.uuid,
672
+ user_id: rand(1000)
673
+ }
674
+ }
675
+ end
676
+
677
+ # Apply filter if provided
678
+ if filter
679
+ logs = logs.select { |log| log[:message].include?(filter) || log[:level] == filter.upcase }
680
+ end
681
+
682
+ {
683
+ source: "application",
684
+ filter: filter,
685
+ count: logs.count,
686
+ logs: logs
687
+ }.to_json
688
+ end
689
+ end
690
+
691
+ # Configuration management tool
692
+ class ConfigTool < Tsikol::Tool
693
+ name "config"
694
+ description "Manage application configuration"
695
+
696
+ parameter :action do
697
+ type :string
698
+ required
699
+ enum ["get", "set", "list", "validate", "diff"]
700
+ description "Configuration action"
701
+ end
702
+
703
+ parameter :app do
704
+ type :string
705
+ required
706
+ description "Application name"
707
+ end
708
+
709
+ parameter :env do
710
+ type :string
711
+ optional
712
+ default "production"
713
+ description "Environment"
714
+ end
715
+
716
+ parameter :key do
717
+ type :string
718
+ optional
719
+ description "Configuration key"
720
+ end
721
+
722
+ parameter :value do
723
+ type :string
724
+ optional
725
+ description "Configuration value"
726
+ end
727
+
728
+ def execute(action:, app:, env: "production", key: nil, value: nil)
729
+ config_store = ConfigStore.new(app, env)
730
+
731
+ case action
732
+ when "get"
733
+ config_store.get(key)
734
+ when "set"
735
+ raise "Key and value required for set action" unless key && value
736
+ config_store.set(key, value)
737
+ when "list"
738
+ config_store.list
739
+ when "validate"
740
+ config_store.validate
741
+ when "diff"
742
+ config_store.diff(params[:compare_env] || "staging")
743
+ end
744
+ end
745
+ end
746
+
747
+ # Configuration store
748
+ class ConfigStore
749
+ def initialize(app, env)
750
+ @app = app
751
+ @env = env
752
+ @config_dir = "config/apps/#{app}"
753
+ @config_file = "#{@config_dir}/#{env}.yml"
754
+ end
755
+
756
+ def get(key)
757
+ config = load_config
758
+
759
+ if key
760
+ value = key.split('.').reduce(config) { |hash, k| hash[k] if hash }
761
+ value ? "#{key}: #{value}" : "Key not found: #{key}"
762
+ else
763
+ format_config(config)
764
+ end
765
+ end
766
+
767
+ def set(key, value)
768
+ config = load_config
769
+
770
+ # Navigate to the key and set value
771
+ keys = key.split('.')
772
+ last_key = keys.pop
773
+
774
+ hash = keys.reduce(config) { |h, k| h[k] ||= {} }
775
+ hash[last_key] = value
776
+
777
+ save_config(config)
778
+
779
+ "Configuration updated: #{key} = #{value}"
780
+ end
781
+
782
+ def list
783
+ config = load_config
784
+ format_config(config)
785
+ end
786
+
787
+ def validate
788
+ config = load_config
789
+ schema_file = "#{@config_dir}/schema.yml"
790
+
791
+ if File.exist?(schema_file)
792
+ schema = YAML.load_file(schema_file)
793
+ errors = validate_against_schema(config, schema)
794
+
795
+ if errors.empty?
796
+ "Configuration is valid"
797
+ else
798
+ "Validation errors:\n" + errors.join("\n")
799
+ end
800
+ else
801
+ "No schema found for validation"
802
+ end
803
+ end
804
+
805
+ private
806
+
807
+ def load_config
808
+ FileUtils.mkdir_p(@config_dir)
809
+
810
+ if File.exist?(@config_file)
811
+ YAML.load_file(@config_file) || {}
812
+ else
813
+ {}
814
+ end
815
+ end
816
+
817
+ def save_config(config)
818
+ File.write(@config_file, config.to_yaml)
819
+ end
820
+
821
+ def format_config(config, prefix = "")
822
+ lines = []
823
+
824
+ config.each do |key, value|
825
+ full_key = prefix.empty? ? key : "#{prefix}.#{key}"
826
+
827
+ if value.is_a?(Hash)
828
+ lines.concat(format_config(value, full_key))
829
+ else
830
+ lines << "#{full_key}: #{value}"
831
+ end
832
+ end
833
+
834
+ lines.join("\n")
835
+ end
836
+ end
837
+
838
+ # DevOps assistant prompt
839
+ class DevOpsAssistantPrompt < Tsikol::Prompt
840
+ name "devops_assistant"
841
+ description "Expert DevOps automation assistant"
842
+
843
+ argument :specialization do
844
+ type :string
845
+ required
846
+ enum ["containers", "kubernetes", "ci_cd", "monitoring", "security"]
847
+ description "Area of specialization"
848
+ end
849
+
850
+ argument :experience_level do
851
+ type :string
852
+ optional
853
+ default "intermediate"
854
+ enum ["beginner", "intermediate", "advanced"]
855
+ description "User's DevOps experience level"
856
+ end
857
+
858
+ def get_messages(specialization:, experience_level: "intermediate")
859
+ [
860
+ {
861
+ role: "system",
862
+ content: {
863
+ type: "text",
864
+ text: build_system_prompt(specialization, experience_level)
865
+ }
866
+ },
867
+ {
868
+ role: "user",
869
+ content: {
870
+ type: "text",
871
+ text: "Initialize DevOps assistant"
872
+ }
873
+ },
874
+ {
875
+ role: "assistant",
876
+ content: {
877
+ type: "text",
878
+ text: "I'm ready to help with your #{specialization.gsub('_', '/')} needs. I can assist with automation, troubleshooting, best practices, and optimization. What DevOps challenge are you working on?"
879
+ }
880
+ }
881
+ ]
882
+ end
883
+
884
+ private
885
+
886
+ def build_system_prompt(specialization, level)
887
+ base = <<~PROMPT
888
+ You are an expert DevOps engineer specializing in #{specialization.gsub('_', ' ')}.
889
+
890
+ Available tools:
891
+ - docker: Manage containers and images
892
+ - kubernetes: Deploy and manage Kubernetes resources
893
+ - pipeline: Execute CI/CD pipelines
894
+ - deploy: Automated deployment operations
895
+ - config: Configuration management
896
+
897
+ User level: #{level}
898
+
899
+ Key responsibilities:
900
+ 1. Automate infrastructure and deployment processes
901
+ 2. Implement best practices for reliability and security
902
+ 3. Optimize performance and resource usage
903
+ 4. Troubleshoot issues systematically
904
+ 5. Guide on tool selection and architecture
905
+ PROMPT
906
+
907
+ base + specialization_details(specialization)
908
+ end
909
+
910
+ def specialization_details(spec)
911
+ case spec
912
+ when "containers"
913
+ "\n\nContainer expertise:\n- Docker best practices\n- Image optimization\n- Security scanning\n- Multi-stage builds\n- Registry management"
914
+ when "kubernetes"
915
+ "\n\nKubernetes expertise:\n- Resource management\n- Scaling strategies\n- Service mesh\n- Observability\n- Security policies"
916
+ when "ci_cd"
917
+ "\n\nCI/CD expertise:\n- Pipeline optimization\n- Testing strategies\n- Artifact management\n- Deployment automation\n- GitOps practices"
918
+ when "monitoring"
919
+ "\n\nMonitoring expertise:\n- Metrics collection\n- Log aggregation\n- Alerting strategies\n- Performance analysis\n- Incident response"
920
+ else
921
+ ""
922
+ end
923
+ end
924
+ end
925
+
926
+ # Start the server
927
+ Tsikol.start(
928
+ name: "devops-tools",
929
+ version: "1.0.0",
930
+ description: "Comprehensive DevOps automation MCP server"
931
+ ) do
932
+ # Enable capabilities
933
+ logging true
934
+ prompts true
935
+
936
+ # Middleware
937
+ use Tsikol::LoggingMiddleware, level: :info
938
+ use Tsikol::AuthenticationMiddleware,
939
+ type: :api_key,
940
+ validator: ->(key) { validate_api_key(key) }
941
+ use Tsikol::RateLimitMiddleware,
942
+ max_requests: 100,
943
+ window_seconds: 60
944
+
945
+ # Tools
946
+ tool DockerTool
947
+ tool KubernetesTool
948
+ tool PipelineTool
949
+ tool DeploymentTool
950
+ tool ConfigTool
951
+
952
+ # Resources
953
+ resource MonitoringResource
954
+ resource LogAggregatorResource
955
+
956
+ # Prompts
957
+ prompt DevOpsAssistantPrompt
958
+
959
+ # Health check endpoint
960
+ resource "health" do
961
+ description "Service health status"
962
+
963
+ def read
964
+ {
965
+ status: "healthy",
966
+ timestamp: Time.now.iso8601,
967
+ version: "1.0.0",
968
+ checks: {
969
+ docker: docker_healthy?,
970
+ kubernetes: kubernetes_healthy?
971
+ }
972
+ }.to_json
973
+ end
974
+
975
+ def docker_healthy?
976
+ Docker.version
977
+ true
978
+ rescue
979
+ false
980
+ end
981
+
982
+ def kubernetes_healthy?
983
+ # Check if kubectl is available
984
+ system("kubectl version --client > /dev/null 2>&1")
985
+ end
986
+ end
987
+
988
+ def validate_api_key(key)
989
+ # In production, validate against secure key store
990
+ valid_keys = {
991
+ "admin-key" => { role: "admin", rate_limit: 1000 },
992
+ "user-key" => { role: "user", rate_limit: 100 }
993
+ }
994
+
995
+ valid_keys[key]
996
+ end
997
+ end
998
+ ```
999
+
1000
+ ### Pipeline Configuration Example
1001
+
1002
+ ```yaml
1003
+ # .pipeline.yml
1004
+ name: microservice-pipeline
1005
+ description: CI/CD pipeline for microservice
1006
+
1007
+ env:
1008
+ DOCKER_REGISTRY: registry.example.com
1009
+ APP_NAME: my-service
1010
+
1011
+ stages:
1012
+ - name: test
1013
+ script:
1014
+ - echo "Running tests..."
1015
+ - bundle install
1016
+ - bundle exec rspec
1017
+ - bundle exec rubocop
1018
+
1019
+ - name: build
1020
+ script:
1021
+ - echo "Building Docker image..."
1022
+ - docker build -t $DOCKER_REGISTRY/$APP_NAME:$CI_COMMIT_SHA .
1023
+ - docker tag $DOCKER_REGISTRY/$APP_NAME:$CI_COMMIT_SHA $DOCKER_REGISTRY/$APP_NAME:latest
1024
+ only:
1025
+ - main
1026
+ - develop
1027
+
1028
+ - name: push
1029
+ script:
1030
+ - echo "Pushing to registry..."
1031
+ - docker push $DOCKER_REGISTRY/$APP_NAME:$CI_COMMIT_SHA
1032
+ - docker push $DOCKER_REGISTRY/$APP_NAME:latest
1033
+ only:
1034
+ - main
1035
+
1036
+ - name: deploy-staging
1037
+ script:
1038
+ - echo "Deploying to staging..."
1039
+ - kubectl set image deployment/$APP_NAME $APP_NAME=$DOCKER_REGISTRY/$APP_NAME:$CI_COMMIT_SHA -n staging
1040
+ - kubectl rollout status deployment/$APP_NAME -n staging
1041
+ only:
1042
+ - develop
1043
+ environment: staging
1044
+
1045
+ - name: deploy-production
1046
+ script:
1047
+ - echo "Deploying to production..."
1048
+ - kubectl set image deployment/$APP_NAME $APP_NAME=$DOCKER_REGISTRY/$APP_NAME:$CI_COMMIT_SHA -n production
1049
+ - kubectl rollout status deployment/$APP_NAME -n production
1050
+ only:
1051
+ - main
1052
+ environment: production
1053
+ when: manual
1054
+ ```
1055
+
1056
+ ### Kubernetes Deployment Example
1057
+
1058
+ ```yaml
1059
+ # deploy/production/my-service.yml
1060
+ apiVersion: apps/v1
1061
+ kind: Deployment
1062
+ metadata:
1063
+ name: my-service
1064
+ namespace: production
1065
+ spec:
1066
+ replicas: 3
1067
+ selector:
1068
+ matchLabels:
1069
+ app: my-service
1070
+ template:
1071
+ metadata:
1072
+ labels:
1073
+ app: my-service
1074
+ spec:
1075
+ containers:
1076
+ - name: my-service
1077
+ image: registry.example.com/my-service:latest
1078
+ ports:
1079
+ - containerPort: 3000
1080
+ env:
1081
+ - name: RAILS_ENV
1082
+ value: production
1083
+ - name: DATABASE_URL
1084
+ valueFrom:
1085
+ secretKeyRef:
1086
+ name: my-service-secrets
1087
+ key: database-url
1088
+ resources:
1089
+ requests:
1090
+ memory: "256Mi"
1091
+ cpu: "100m"
1092
+ limits:
1093
+ memory: "512Mi"
1094
+ cpu: "500m"
1095
+ livenessProbe:
1096
+ httpGet:
1097
+ path: /health
1098
+ port: 3000
1099
+ initialDelaySeconds: 30
1100
+ periodSeconds: 10
1101
+ readinessProbe:
1102
+ httpGet:
1103
+ path: /ready
1104
+ port: 3000
1105
+ initialDelaySeconds: 5
1106
+ periodSeconds: 5
1107
+ ---
1108
+ apiVersion: v1
1109
+ kind: Service
1110
+ metadata:
1111
+ name: my-service
1112
+ namespace: production
1113
+ spec:
1114
+ selector:
1115
+ app: my-service
1116
+ ports:
1117
+ - protocol: TCP
1118
+ port: 80
1119
+ targetPort: 3000
1120
+ ---
1121
+ apiVersion: networking.k8s.io/v1
1122
+ kind: Ingress
1123
+ metadata:
1124
+ name: my-service
1125
+ namespace: production
1126
+ annotations:
1127
+ kubernetes.io/ingress.class: nginx
1128
+ cert-manager.io/cluster-issuer: letsencrypt-prod
1129
+ spec:
1130
+ tls:
1131
+ - hosts:
1132
+ - api.example.com
1133
+ secretName: my-service-tls
1134
+ rules:
1135
+ - host: api.example.com
1136
+ http:
1137
+ paths:
1138
+ - path: /
1139
+ pathType: Prefix
1140
+ backend:
1141
+ service:
1142
+ name: my-service
1143
+ port:
1144
+ number: 80
1145
+ ```
1146
+
1147
+ ### Testing
1148
+
1149
+ ```ruby
1150
+ require 'minitest/autorun'
1151
+ require 'tsikol/test_helpers'
1152
+
1153
+ class DevOpsToolsTest < Minitest::Test
1154
+ include Tsikol::TestHelpers
1155
+
1156
+ def setup
1157
+ @server = create_test_server
1158
+ @client = TestClient.new(@server)
1159
+
1160
+ # Mock Docker and Kubernetes
1161
+ setup_mocks
1162
+ end
1163
+
1164
+ def test_docker_list_containers
1165
+ response = @client.call_tool("docker", {
1166
+ "operation" => "list",
1167
+ "options" => { "all" => true }
1168
+ })
1169
+
1170
+ assert_successful_response(response)
1171
+ result = response.dig(:result, :content, 0, :text)
1172
+ assert_match /CONTAINER ID/, result
1173
+ end
1174
+
1175
+ def test_kubernetes_get_pods
1176
+ response = @client.call_tool("kubernetes", {
1177
+ "operation" => "get",
1178
+ "resource_type" => "pod",
1179
+ "options" => { "namespace" => "default" }
1180
+ })
1181
+
1182
+ assert_successful_response(response)
1183
+ result = response.dig(:result, :content, 0, :text)
1184
+ assert_match /NAME.*READY.*STATUS/, result
1185
+ end
1186
+
1187
+ def test_pipeline_execution
1188
+ # Create test pipeline file
1189
+ File.write("test.pipeline.yml", {
1190
+ "name" => "test-pipeline",
1191
+ "stages" => [
1192
+ {
1193
+ "name" => "test",
1194
+ "script" => ["echo 'Running tests'", "exit 0"]
1195
+ }
1196
+ ]
1197
+ }.to_yaml)
1198
+
1199
+ response = @client.call_tool("pipeline", {
1200
+ "action" => "run",
1201
+ "pipeline_file" => "test.pipeline.yml"
1202
+ })
1203
+
1204
+ assert_successful_response(response)
1205
+ result = response.dig(:result, :content, 0, :text)
1206
+ assert_match /test: passed/, result
1207
+ ensure
1208
+ File.delete("test.pipeline.yml") if File.exist?("test.pipeline.yml")
1209
+ end
1210
+
1211
+ def test_deployment_simulation
1212
+ response = @client.call_tool("deploy", {
1213
+ "environment" => "staging",
1214
+ "service" => "test-service",
1215
+ "version" => "v1.2.3",
1216
+ "strategy" => "rolling"
1217
+ })
1218
+
1219
+ assert_successful_response(response)
1220
+ result = response.dig(:result, :content, 0, :text)
1221
+ assert_match /Deployment completed successfully/, result
1222
+ end
1223
+
1224
+ def test_monitoring_metrics
1225
+ response = @client.read_resource("monitoring/system/cpu")
1226
+
1227
+ assert_successful_response(response)
1228
+ metrics = JSON.parse(response.dig(:result, :contents, 0, :text))
1229
+
1230
+ assert metrics["cpu"]
1231
+ assert metrics["cpu"]["usage_percent"]
1232
+ end
1233
+
1234
+ private
1235
+
1236
+ def create_test_server
1237
+ Tsikol::Server.new(name: "test-devops") do
1238
+ tool DockerTool
1239
+ tool KubernetesTool
1240
+ tool PipelineTool
1241
+ tool DeploymentTool
1242
+ resource MonitoringResource
1243
+ end
1244
+ end
1245
+
1246
+ def setup_mocks
1247
+ # Mock Docker responses
1248
+ Docker::Container.define_singleton_method(:all) do |options|
1249
+ [
1250
+ MockContainer.new("abc123", "nginx:latest", "Up 2 hours", ["web"])
1251
+ ]
1252
+ end
1253
+
1254
+ # Mock Kubernetes client
1255
+ # Would need actual mock implementation
1256
+ end
1257
+ end
1258
+
1259
+ class MockContainer
1260
+ attr_reader :info
1261
+
1262
+ def initialize(id, image, status, names)
1263
+ @info = {
1264
+ "Id" => id,
1265
+ "Image" => image,
1266
+ "Status" => status,
1267
+ "Names" => names.map { |n| "/#{n}" }
1268
+ }
1269
+ end
1270
+ end
1271
+ ```
1272
+
1273
+ ## Best Practices Demonstrated
1274
+
1275
+ 1. **Infrastructure as Code** - Configuration-driven deployments
1276
+ 2. **Container Management** - Docker best practices
1277
+ 3. **Kubernetes Operations** - Production-ready deployments
1278
+ 4. **CI/CD Automation** - Pipeline-driven development
1279
+ 5. **Monitoring Integration** - Comprehensive metrics
1280
+ 6. **Security First** - API key validation, secure deployments
1281
+ 7. **Error Handling** - Graceful degradation
1282
+ 8. **Logging** - Structured logging for debugging
1283
+ 9. **Testing** - Automated testing of DevOps operations
1284
+ 10. **Documentation** - Self-documenting configurations
1285
+
1286
+ ## Advanced Features
1287
+
1288
+ ### GitOps Integration
1289
+
1290
+ ```ruby
1291
+ class GitOpsTool < Tsikol::Tool
1292
+ name "gitops"
1293
+ description "GitOps workflow management"
1294
+
1295
+ parameter :action do
1296
+ type :string
1297
+ required
1298
+ enum ["sync", "diff", "rollback", "promote"]
1299
+ end
1300
+
1301
+ def execute(action:, **options)
1302
+ case action
1303
+ when "sync"
1304
+ sync_with_git(options)
1305
+ when "diff"
1306
+ show_git_diff(options)
1307
+ when "rollback"
1308
+ rollback_deployment(options)
1309
+ when "promote"
1310
+ promote_environment(options)
1311
+ end
1312
+ end
1313
+
1314
+ private
1315
+
1316
+ def sync_with_git(options)
1317
+ # Pull latest from git
1318
+ # Apply to cluster
1319
+ # Update status
1320
+ end
1321
+ end
1322
+ ```
1323
+
1324
+ ### Secret Management
1325
+
1326
+ ```ruby
1327
+ class SecretsTool < Tsikol::Tool
1328
+ name "secrets"
1329
+ description "Secure secret management"
1330
+
1331
+ parameter :action do
1332
+ type :string
1333
+ required
1334
+ enum ["create", "read", "update", "rotate", "list"]
1335
+ end
1336
+
1337
+ parameter :backend do
1338
+ type :string
1339
+ optional
1340
+ default "vault"
1341
+ enum ["vault", "kubernetes", "aws-secrets-manager"]
1342
+ end
1343
+
1344
+ def execute(action:, backend: "vault", **options)
1345
+ secret_manager = create_backend(backend)
1346
+
1347
+ case action
1348
+ when "create"
1349
+ secret_manager.create(options)
1350
+ when "read"
1351
+ secret_manager.read(options)
1352
+ when "rotate"
1353
+ secret_manager.rotate(options)
1354
+ end
1355
+ end
1356
+ end
1357
+ ```
1358
+
1359
+ ## Security Considerations
1360
+
1361
+ 1. **RBAC** - Role-based access control for all operations
1362
+ 2. **Audit Logging** - Track all DevOps operations
1363
+ 3. **Secret Management** - Never expose secrets in logs
1364
+ 4. **Network Policies** - Restrict cluster communications
1365
+ 5. **Image Scanning** - Scan containers for vulnerabilities
1366
+
1367
+ ## Performance Optimization
1368
+
1369
+ 1. **Parallel Execution** - Run independent tasks concurrently
1370
+ 2. **Caching** - Cache build artifacts and test results
1371
+ 3. **Resource Limits** - Set appropriate limits for all containers
1372
+ 4. **Auto-scaling** - Scale based on metrics
1373
+ 5. **Optimized Images** - Multi-stage builds, minimal base images
1374
+
1375
+ ## Next Steps
1376
+
1377
+ - Add Terraform integration for infrastructure
1378
+ - Implement chaos engineering tools
1379
+ - Add cost optimization features
1380
+ - Create compliance checking tools
1381
+ - Implement disaster recovery automation
1382
+ - Add multi-cloud support