robot_lab 0.0.1

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 (153) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +1 -0
  3. data/.github/workflows/deploy-github-pages.yml +52 -0
  4. data/.github/workflows/deploy-yard-docs.yml +52 -0
  5. data/CHANGELOG.md +55 -0
  6. data/COMMITS.md +196 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +332 -0
  9. data/Rakefile +67 -0
  10. data/docs/api/adapters/anthropic.md +121 -0
  11. data/docs/api/adapters/gemini.md +133 -0
  12. data/docs/api/adapters/index.md +104 -0
  13. data/docs/api/adapters/openai.md +134 -0
  14. data/docs/api/core/index.md +113 -0
  15. data/docs/api/core/memory.md +314 -0
  16. data/docs/api/core/network.md +291 -0
  17. data/docs/api/core/robot.md +273 -0
  18. data/docs/api/core/state.md +273 -0
  19. data/docs/api/core/tool.md +353 -0
  20. data/docs/api/history/active-record-adapter.md +195 -0
  21. data/docs/api/history/config.md +191 -0
  22. data/docs/api/history/index.md +132 -0
  23. data/docs/api/history/thread-manager.md +144 -0
  24. data/docs/api/index.md +82 -0
  25. data/docs/api/mcp/client.md +221 -0
  26. data/docs/api/mcp/index.md +111 -0
  27. data/docs/api/mcp/server.md +225 -0
  28. data/docs/api/mcp/transports.md +264 -0
  29. data/docs/api/messages/index.md +67 -0
  30. data/docs/api/messages/text-message.md +102 -0
  31. data/docs/api/messages/tool-call-message.md +144 -0
  32. data/docs/api/messages/tool-result-message.md +154 -0
  33. data/docs/api/messages/user-message.md +171 -0
  34. data/docs/api/streaming/context.md +174 -0
  35. data/docs/api/streaming/events.md +237 -0
  36. data/docs/api/streaming/index.md +108 -0
  37. data/docs/architecture/core-concepts.md +243 -0
  38. data/docs/architecture/index.md +138 -0
  39. data/docs/architecture/message-flow.md +320 -0
  40. data/docs/architecture/network-orchestration.md +216 -0
  41. data/docs/architecture/robot-execution.md +243 -0
  42. data/docs/architecture/state-management.md +323 -0
  43. data/docs/assets/css/custom.css +56 -0
  44. data/docs/assets/images/robot_lab.jpg +0 -0
  45. data/docs/concepts.md +216 -0
  46. data/docs/examples/basic-chat.md +193 -0
  47. data/docs/examples/index.md +129 -0
  48. data/docs/examples/mcp-server.md +290 -0
  49. data/docs/examples/multi-robot-network.md +312 -0
  50. data/docs/examples/rails-application.md +420 -0
  51. data/docs/examples/tool-usage.md +310 -0
  52. data/docs/getting-started/configuration.md +230 -0
  53. data/docs/getting-started/index.md +56 -0
  54. data/docs/getting-started/installation.md +179 -0
  55. data/docs/getting-started/quick-start.md +203 -0
  56. data/docs/guides/building-robots.md +376 -0
  57. data/docs/guides/creating-networks.md +366 -0
  58. data/docs/guides/history.md +359 -0
  59. data/docs/guides/index.md +68 -0
  60. data/docs/guides/mcp-integration.md +356 -0
  61. data/docs/guides/memory.md +309 -0
  62. data/docs/guides/rails-integration.md +432 -0
  63. data/docs/guides/streaming.md +314 -0
  64. data/docs/guides/using-tools.md +394 -0
  65. data/docs/index.md +160 -0
  66. data/examples/01_simple_robot.rb +38 -0
  67. data/examples/02_tools.rb +106 -0
  68. data/examples/03_network.rb +103 -0
  69. data/examples/04_mcp.rb +219 -0
  70. data/examples/05_streaming.rb +124 -0
  71. data/examples/06_prompt_templates.rb +324 -0
  72. data/examples/07_network_memory.rb +329 -0
  73. data/examples/prompts/assistant/system.txt.erb +2 -0
  74. data/examples/prompts/assistant/user.txt.erb +1 -0
  75. data/examples/prompts/billing/system.txt.erb +7 -0
  76. data/examples/prompts/billing/user.txt.erb +1 -0
  77. data/examples/prompts/classifier/system.txt.erb +4 -0
  78. data/examples/prompts/classifier/user.txt.erb +1 -0
  79. data/examples/prompts/entity_extractor/system.txt.erb +11 -0
  80. data/examples/prompts/entity_extractor/user.txt.erb +3 -0
  81. data/examples/prompts/escalation/system.txt.erb +35 -0
  82. data/examples/prompts/escalation/user.txt.erb +34 -0
  83. data/examples/prompts/general/system.txt.erb +4 -0
  84. data/examples/prompts/general/user.txt.erb +1 -0
  85. data/examples/prompts/github_assistant/system.txt.erb +6 -0
  86. data/examples/prompts/github_assistant/user.txt.erb +1 -0
  87. data/examples/prompts/helper/system.txt.erb +1 -0
  88. data/examples/prompts/helper/user.txt.erb +1 -0
  89. data/examples/prompts/keyword_extractor/system.txt.erb +8 -0
  90. data/examples/prompts/keyword_extractor/user.txt.erb +3 -0
  91. data/examples/prompts/order_support/system.txt.erb +27 -0
  92. data/examples/prompts/order_support/user.txt.erb +22 -0
  93. data/examples/prompts/product_support/system.txt.erb +30 -0
  94. data/examples/prompts/product_support/user.txt.erb +32 -0
  95. data/examples/prompts/sentiment_analyzer/system.txt.erb +9 -0
  96. data/examples/prompts/sentiment_analyzer/user.txt.erb +3 -0
  97. data/examples/prompts/synthesizer/system.txt.erb +14 -0
  98. data/examples/prompts/synthesizer/user.txt.erb +15 -0
  99. data/examples/prompts/technical/system.txt.erb +7 -0
  100. data/examples/prompts/technical/user.txt.erb +1 -0
  101. data/examples/prompts/triage/system.txt.erb +16 -0
  102. data/examples/prompts/triage/user.txt.erb +17 -0
  103. data/lib/generators/robot_lab/install_generator.rb +78 -0
  104. data/lib/generators/robot_lab/robot_generator.rb +55 -0
  105. data/lib/generators/robot_lab/templates/initializer.rb.tt +41 -0
  106. data/lib/generators/robot_lab/templates/migration.rb.tt +32 -0
  107. data/lib/generators/robot_lab/templates/result_model.rb.tt +52 -0
  108. data/lib/generators/robot_lab/templates/robot.rb.tt +46 -0
  109. data/lib/generators/robot_lab/templates/robot_test.rb.tt +32 -0
  110. data/lib/generators/robot_lab/templates/routing_robot.rb.tt +53 -0
  111. data/lib/generators/robot_lab/templates/thread_model.rb.tt +40 -0
  112. data/lib/robot_lab/adapters/anthropic.rb +163 -0
  113. data/lib/robot_lab/adapters/base.rb +85 -0
  114. data/lib/robot_lab/adapters/gemini.rb +193 -0
  115. data/lib/robot_lab/adapters/openai.rb +159 -0
  116. data/lib/robot_lab/adapters/registry.rb +81 -0
  117. data/lib/robot_lab/configuration.rb +143 -0
  118. data/lib/robot_lab/error.rb +32 -0
  119. data/lib/robot_lab/errors.rb +70 -0
  120. data/lib/robot_lab/history/active_record_adapter.rb +146 -0
  121. data/lib/robot_lab/history/config.rb +115 -0
  122. data/lib/robot_lab/history/thread_manager.rb +93 -0
  123. data/lib/robot_lab/mcp/client.rb +210 -0
  124. data/lib/robot_lab/mcp/server.rb +84 -0
  125. data/lib/robot_lab/mcp/transports/base.rb +56 -0
  126. data/lib/robot_lab/mcp/transports/sse.rb +117 -0
  127. data/lib/robot_lab/mcp/transports/stdio.rb +133 -0
  128. data/lib/robot_lab/mcp/transports/streamable_http.rb +139 -0
  129. data/lib/robot_lab/mcp/transports/websocket.rb +108 -0
  130. data/lib/robot_lab/memory.rb +882 -0
  131. data/lib/robot_lab/memory_change.rb +123 -0
  132. data/lib/robot_lab/message.rb +357 -0
  133. data/lib/robot_lab/network.rb +350 -0
  134. data/lib/robot_lab/rails/engine.rb +29 -0
  135. data/lib/robot_lab/rails/railtie.rb +42 -0
  136. data/lib/robot_lab/robot.rb +560 -0
  137. data/lib/robot_lab/robot_result.rb +205 -0
  138. data/lib/robot_lab/robotic_model.rb +324 -0
  139. data/lib/robot_lab/state_proxy.rb +188 -0
  140. data/lib/robot_lab/streaming/context.rb +144 -0
  141. data/lib/robot_lab/streaming/events.rb +95 -0
  142. data/lib/robot_lab/streaming/sequence_counter.rb +48 -0
  143. data/lib/robot_lab/task.rb +117 -0
  144. data/lib/robot_lab/tool.rb +223 -0
  145. data/lib/robot_lab/tool_config.rb +112 -0
  146. data/lib/robot_lab/tool_manifest.rb +234 -0
  147. data/lib/robot_lab/user_message.rb +118 -0
  148. data/lib/robot_lab/version.rb +5 -0
  149. data/lib/robot_lab/waiter.rb +73 -0
  150. data/lib/robot_lab.rb +195 -0
  151. data/mkdocs.yml +214 -0
  152. data/sig/robot_lab.rbs +4 -0
  153. metadata +442 -0
data/docs/index.md ADDED
@@ -0,0 +1,160 @@
1
+ # RobotLab
2
+
3
+ > [!CAUTION]
4
+ > This gem is under active development. APIs and features may change without notice. See the [CHANGELOG](https://github.com/MadBomber/robot_lab/blob/main/CHANGELOG.md) for details.
5
+
6
+ <table>
7
+ <tr>
8
+ <td width="50%" align="center" valign="top">
9
+ <img src="assets/images/robot_lab.jpg" alt="RobotLab"><br>
10
+ <em>"Build robots. Solve problems."</em>
11
+ </td>
12
+ <td width="50%" valign="top">
13
+ RobotLab is a Ruby gem that enables you to build sophisticated AI applications using multiple specialized robots (LLM agents) that work together to accomplish complex tasks.<br><br>
14
+ Each robot has its own system prompt, tools, and capabilities. Robots can be orchestrated through networks with customizable routing logic, share information through a hierarchical memory system, and connect to external tools via the Model Context Protocol (MCP).
15
+ </td>
16
+ </tr>
17
+ </table>
18
+
19
+ ## Key Features
20
+
21
+ <div class="grid cards" markdown>
22
+
23
+ - :material-robot:{ .lg .middle } **Multi-Robot Architecture**
24
+
25
+ ---
26
+
27
+ Build applications with multiple specialized AI agents, each with unique capabilities and personalities.
28
+
29
+ [:octicons-arrow-right-24: Learn more](architecture/core-concepts.md)
30
+
31
+ - :material-transit-connection-variant:{ .lg .middle } **Network Orchestration**
32
+
33
+ ---
34
+
35
+ Connect robots in networks with flexible routing to handle complex, multi-step workflows.
36
+
37
+ [:octicons-arrow-right-24: Creating Networks](guides/creating-networks.md)
38
+
39
+ - :material-tools:{ .lg .middle } **Extensible Tools**
40
+
41
+ ---
42
+
43
+ Give robots custom tools to interact with external systems, databases, and APIs.
44
+
45
+ [:octicons-arrow-right-24: Using Tools](guides/using-tools.md)
46
+
47
+ - :material-server-network:{ .lg .middle } **MCP Integration**
48
+
49
+ ---
50
+
51
+ Connect to Model Context Protocol servers to extend robot capabilities with external tools.
52
+
53
+ [:octicons-arrow-right-24: MCP Guide](guides/mcp-integration.md)
54
+
55
+ - :material-memory:{ .lg .middle } **Shared Memory**
56
+
57
+ ---
58
+
59
+ Robots can share information through a hierarchical memory system with namespaced scopes.
60
+
61
+ [:octicons-arrow-right-24: Memory System](guides/memory.md)
62
+
63
+ - :material-history:{ .lg .middle } **Conversation History**
64
+
65
+ ---
66
+
67
+ Persist and restore conversation threads for long-running interactions.
68
+
69
+ [:octicons-arrow-right-24: History Guide](guides/history.md)
70
+
71
+ </div>
72
+
73
+ ## Quick Example
74
+
75
+ ```ruby
76
+ require "robot_lab"
77
+
78
+ # Configure RobotLab
79
+ RobotLab.configure do |config|
80
+ config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
81
+ config.default_model = "claude-sonnet-4"
82
+ end
83
+
84
+ # Create a simple robot
85
+ robot = RobotLab.build do
86
+ name "assistant"
87
+ description "A helpful AI assistant"
88
+ template <<~PROMPT
89
+ You are a helpful assistant. Answer questions clearly and concisely.
90
+ PROMPT
91
+ end
92
+
93
+ # Create a network with the robot
94
+ network = RobotLab.create_network do
95
+ name "simple_network"
96
+ add_robot robot
97
+ end
98
+
99
+ # Run the network
100
+ state = RobotLab.create_state(message: "What is the capital of France?")
101
+ result = network.run(state: state)
102
+
103
+ puts result.last_result.output.first.content
104
+ # => "The capital of France is Paris."
105
+ ```
106
+
107
+ ## Supported LLM Providers
108
+
109
+ RobotLab supports multiple LLM providers through the [ruby_llm](https://github.com/crmne/ruby_llm) library:
110
+
111
+ | Provider | Models |
112
+ |----------|--------|
113
+ | **Anthropic** | Claude 4, Claude Sonnet, Claude Haiku |
114
+ | **OpenAI** | GPT-4o, GPT-4, GPT-3.5 Turbo |
115
+ | **Google** | Gemini Pro, Gemini Ultra |
116
+ | **Azure OpenAI** | All Azure-hosted OpenAI models |
117
+ | **Bedrock** | Claude models via AWS Bedrock |
118
+ | **Ollama** | Local models via Ollama |
119
+
120
+ ## Installation
121
+
122
+ Add RobotLab to your Gemfile:
123
+
124
+ ```ruby
125
+ gem "robot_lab"
126
+ ```
127
+
128
+ Or install directly:
129
+
130
+ ```bash
131
+ gem install robot_lab
132
+ ```
133
+
134
+ [:octicons-arrow-right-24: Full Installation Guide](getting-started/installation.md)
135
+
136
+ ## Next Steps
137
+
138
+ <div class="grid cards" markdown>
139
+
140
+ - [:octicons-rocket-24: **Quick Start**](getting-started/quick-start.md)
141
+
142
+ Get up and running in 5 minutes
143
+
144
+ - [:octicons-book-24: **Concepts**](concepts.md)
145
+
146
+ Understand the core concepts
147
+
148
+ - [:octicons-code-24: **Examples**](examples/index.md)
149
+
150
+ See RobotLab in action
151
+
152
+ - [:octicons-gear-24: **API Reference**](api/index.md)
153
+
154
+ Detailed API documentation
155
+
156
+ </div>
157
+
158
+ ## License
159
+
160
+ RobotLab is released under the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example 1: Simple Robot
5
+ #
6
+ # Demonstrates creating and running a basic robot with a template.
7
+ #
8
+ # Usage:
9
+ # ANTHROPIC_API_KEY=your_key ruby examples/01_simple_robot.rb
10
+
11
+ require_relative "../lib/robot_lab"
12
+
13
+ # Configure RobotLab
14
+ RobotLab.configure do |config|
15
+ config.anthropic_api_key = ENV.fetch("ANTHROPIC_API_KEY", nil)
16
+ config.template_path = File.join(__dir__, "prompts")
17
+ end
18
+
19
+ # Create a simple robot using a template
20
+ robot = RobotLab.build(
21
+ name: "helper",
22
+ template: :helper,
23
+ model: "claude-sonnet-4"
24
+ )
25
+
26
+ puts "Running simple robot..."
27
+ puts "-" * 40
28
+
29
+ # Run the robot with a simple query
30
+ result = robot.run(message: "What is 2 + 2? Please explain your reasoning briefly.")
31
+
32
+ # Display the result
33
+ puts "Robot: #{robot.name}"
34
+ puts "Output:"
35
+ result.output.each do |message|
36
+ puts " #{message.content}" if message.respond_to?(:content)
37
+ end
38
+ puts "-" * 40
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example 2: Robot with Tools
5
+ #
6
+ # Demonstrates creating a robot with custom tools using RubyLLM::Tool.
7
+ #
8
+ # Usage:
9
+ # ANTHROPIC_API_KEY=your_key ruby examples/02_tools.rb
10
+
11
+ require_relative "../lib/robot_lab"
12
+
13
+ # Configure RobotLab
14
+ RobotLab.configure do |config|
15
+ config.anthropic_api_key = ENV.fetch("ANTHROPIC_API_KEY", nil)
16
+ config.template_path = File.join(__dir__, "prompts")
17
+ end
18
+
19
+ # Define tools using RubyLLM::Tool
20
+ class Calculator < RubyLLM::Tool
21
+ description "Performs basic arithmetic operations"
22
+
23
+ param :operation,
24
+ type: "string",
25
+ desc: "The operation to perform (add, subtract, multiply, divide)"
26
+
27
+ param :a,
28
+ type: "number",
29
+ desc: "First operand"
30
+
31
+ param :b,
32
+ type: "number",
33
+ desc: "Second operand"
34
+
35
+ def execute(operation:, a:, b:)
36
+ case operation
37
+ when "add" then a + b
38
+ when "subtract" then a - b
39
+ when "multiply" then a * b
40
+ when "divide" then a.to_f / b
41
+ else "Unknown operation: #{operation}"
42
+ end
43
+ end
44
+ end
45
+
46
+ class FortuneCookie < RubyLLM::Tool
47
+ description "Get a fortune cookie message with wisdom and lucky numbers"
48
+
49
+ param :category,
50
+ type: "string",
51
+ desc: "The category of fortune (wisdom, love, career, adventure)"
52
+
53
+ FORTUNES = {
54
+ "wisdom" => [
55
+ "The obstacle in the path becomes the path.",
56
+ "A journey of a thousand miles begins with a single step.",
57
+ "The best time to plant a tree was 20 years ago. The second best time is now."
58
+ ],
59
+ "love" => [
60
+ "The heart that loves is always young.",
61
+ "To love and be loved is to feel the sun from both sides.",
62
+ "Love is not about finding the right person, but being the right person."
63
+ ],
64
+ "career" => [
65
+ "Opportunity dances with those already on the dance floor.",
66
+ "Your work is your signature. Sign it with excellence.",
67
+ "The expert in anything was once a beginner."
68
+ ],
69
+ "adventure" => [
70
+ "Life shrinks or expands in proportion to one's courage.",
71
+ "Not all who wander are lost.",
72
+ "The biggest adventure you can take is to live the life of your dreams."
73
+ ]
74
+ }.freeze
75
+
76
+ def execute(category:)
77
+ {
78
+ category: category,
79
+ fortune: FORTUNES.fetch(category, FORTUNES["wisdom"]).sample,
80
+ lucky_numbers: Array.new(6) { rand(1..49) }.sort
81
+ }
82
+ end
83
+ end
84
+
85
+ # Create robot with tools
86
+ robot = RobotLab.build(
87
+ name: "assistant",
88
+ template: :assistant,
89
+ tools: [Calculator, FortuneCookie],
90
+ model: "claude-sonnet-4"
91
+ )
92
+
93
+ puts "Running robot with tools..."
94
+ puts "-" * 40
95
+
96
+ # Run the robot
97
+ result = robot.run(message: "What is 15 multiplied by 7? Also, give me a fortune about my career.")
98
+
99
+ # Display results
100
+ puts "Robot: #{robot.name}"
101
+ puts "\nOutput:"
102
+ result.output.each do |message|
103
+ puts " #{message.content}" if message.respond_to?(:content)
104
+ end
105
+
106
+ puts "-" * 40
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example 3: Multi-Robot Network
5
+ #
6
+ # Demonstrates creating a network of robots with conditional routing
7
+ # using SimpleFlow's optional step activation.
8
+ #
9
+ # Usage:
10
+ # ANTHROPIC_API_KEY=your_key ruby examples/03_network.rb
11
+
12
+ require_relative "../lib/robot_lab"
13
+
14
+ # Configure RobotLab
15
+ RobotLab.configure do |config|
16
+ config.anthropic_api_key = ENV.fetch("ANTHROPIC_API_KEY", nil)
17
+ config.template_path = File.join(__dir__, "prompts")
18
+ end
19
+
20
+ # Classifier robot that activates the appropriate specialist
21
+ class ClassifierRobot < RobotLab::Robot
22
+ def call(result)
23
+ robot_result = run(**extract_run_context(result))
24
+
25
+ new_result = result
26
+ .with_context(@name.to_sym, robot_result)
27
+ .continue(robot_result)
28
+
29
+ # Examine LLM output and activate appropriate specialist
30
+ category = robot_result.last_text_content.to_s.strip.downcase
31
+
32
+ case category
33
+ when /billing/
34
+ new_result.activate(:billing)
35
+ when /technical/
36
+ new_result.activate(:technical)
37
+ else
38
+ new_result.activate(:general)
39
+ end
40
+ end
41
+ end
42
+
43
+ # Create specialized robots
44
+ classifier = ClassifierRobot.new(
45
+ name: "classifier",
46
+ template: :classifier,
47
+ model: "claude-sonnet-4"
48
+ )
49
+
50
+ billing_robot = RobotLab.build(
51
+ name: "billing",
52
+ template: :billing,
53
+ model: "claude-sonnet-4"
54
+ )
55
+
56
+ technical_robot = RobotLab.build(
57
+ name: "technical",
58
+ template: :technical,
59
+ model: "claude-sonnet-4"
60
+ )
61
+
62
+ general_robot = RobotLab.build(
63
+ name: "general",
64
+ template: :general,
65
+ model: "claude-sonnet-4"
66
+ )
67
+
68
+ # Create network with optional task routing
69
+ network = RobotLab.create_network(name: "support_network") do
70
+ task :classifier, classifier, depends_on: :none
71
+ task :billing, billing_robot, depends_on: :optional
72
+ task :technical, technical_robot, depends_on: :optional
73
+ task :general, general_robot, depends_on: :optional
74
+ end
75
+
76
+ puts "Running multi-robot network..."
77
+ puts "-" * 40
78
+ puts "Network structure:"
79
+ puts network.visualize
80
+ puts "-" * 40
81
+
82
+ # Run the network with a billing question
83
+ result = network.run(message: "I was charged twice for my subscription last month. Can you help?")
84
+
85
+ # Display results
86
+ puts "Network: #{network.name}"
87
+ puts "\nConversation flow:"
88
+
89
+ # Show classifier result
90
+ if result.context[:classifier]
91
+ classifier_result = result.context[:classifier]
92
+ puts "\n1. Robot: classifier"
93
+ puts " Classification: #{classifier_result.last_text_content}"
94
+ end
95
+
96
+ # Show specialist result (the final value)
97
+ if result.value.is_a?(RobotLab::RobotResult)
98
+ puts "\n2. Robot: #{result.value.robot_name}"
99
+ content = result.value.last_text_content
100
+ puts " Response: #{content[0..200]}..." if content
101
+ end
102
+
103
+ puts "-" * 40
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example 4: MCP (Model Context Protocol) Integration with GitHub
5
+ #
6
+ # Demonstrates connecting to the GitHub MCP server and using its tools
7
+ # to interact with GitHub repositories.
8
+ #
9
+ # Prerequisites:
10
+ # 1. Install the GitHub MCP server: brew install github-mcp-server
11
+ # 2. Set environment variables:
12
+ # - ANTHROPIC_API_KEY: Your Anthropic API key
13
+ # - GITHUB_PERSONAL_ACCESS_TOKEN: Your GitHub personal access token
14
+ #
15
+ # Usage:
16
+ # ANTHROPIC_API_KEY=your_key GITHUB_PERSONAL_ACCESS_TOKEN=your_token ruby examples/04_mcp.rb
17
+ #
18
+ # The GitHub MCP server provides tools for:
19
+ # - Searching repositories, code, issues, and users
20
+ # - Creating and managing issues and pull requests
21
+ # - Reading file contents and repository information
22
+ # - Managing branches and commits
23
+
24
+ require_relative "../lib/robot_lab"
25
+
26
+ # Configure RobotLab
27
+ RobotLab.configure do |config|
28
+ config.anthropic_api_key = ENV.fetch("ANTHROPIC_API_KEY", nil)
29
+ config.template_path = File.join(__dir__, "prompts")
30
+ end
31
+
32
+ # GitHub MCP server configuration using StdIO transport
33
+ github_server = {
34
+ name: "github",
35
+ transport: {
36
+ type: "stdio",
37
+ command: "github-mcp-server",
38
+ args: ["stdio"],
39
+ env: {
40
+ "GITHUB_PERSONAL_ACCESS_TOKEN" => ENV.fetch("GITHUB_PERSONAL_ACCESS_TOKEN", "")
41
+ }
42
+ }
43
+ }
44
+
45
+ puts <<~HEADER
46
+ MCP Integration Example: GitHub
47
+ #{"=" * 40}
48
+
49
+ This example demonstrates using the GitHub MCP server to interact
50
+ with GitHub repositories through the Model Context Protocol.
51
+
52
+ HEADER
53
+
54
+ # Verify prerequisites
55
+ unless ENV["GITHUB_PERSONAL_ACCESS_TOKEN"]
56
+ puts <<~ERROR
57
+ ERROR: GITHUB_PERSONAL_ACCESS_TOKEN environment variable not set.
58
+
59
+ To use this example:
60
+ 1. Create a GitHub Personal Access Token at https://github.com/settings/tokens
61
+ 2. Grant appropriate permissions (repo, read:user, etc.)
62
+ 3. Run: GITHUB_PERSONAL_ACCESS_TOKEN=your_token ruby examples/04_mcp.rb
63
+ ERROR
64
+ exit 1
65
+ end
66
+
67
+ # ============================================================================
68
+ # Part 1: Direct MCP Client Usage
69
+ # ============================================================================
70
+
71
+ puts "PART 1: Direct MCP Client Usage"
72
+ puts "-" * 40
73
+ puts
74
+ puts "Connecting to GitHub MCP server..."
75
+
76
+ begin
77
+ # Create MCP client and connect
78
+ client = RobotLab::MCP::Client.new(github_server)
79
+ client.connect
80
+
81
+ unless client.connected?
82
+ puts <<~ERROR
83
+
84
+ ERROR: Failed to connect to the GitHub MCP server.
85
+
86
+ Make sure you have installed it:
87
+ brew install github-mcp-server
88
+ ERROR
89
+ exit 1
90
+ end
91
+
92
+ puts "Connected successfully!"
93
+ puts
94
+
95
+ # List available tools
96
+ puts "Available GitHub Tools:"
97
+ puts "-" * 40
98
+
99
+ tools = client.list_tools
100
+ if tools.empty?
101
+ puts " (No tools returned - check server connection)"
102
+ else
103
+ tools.each do |tool|
104
+ puts " #{tool[:name]}"
105
+ puts " #{tool[:description]}" if tool[:description]
106
+ end
107
+ end
108
+
109
+ puts "-" * 40
110
+ puts "Total: #{tools.size} tools available"
111
+ puts
112
+
113
+ # Demonstrate a simple tool call: search for repositories
114
+ puts "Demo: Searching for popular Ruby repositories..."
115
+ puts
116
+
117
+ result = client.call_tool("search_repositories", {
118
+ query: "language:ruby stars:>1000",
119
+ per_page: 5
120
+ })
121
+
122
+ # Extract and pretty print the JSON result
123
+ if result.is_a?(Array) && result.first.is_a?(Hash) && result.first[:text]
124
+ data = JSON.parse(result.first[:text])
125
+ puts JSON.pretty_generate(data)
126
+ else
127
+ puts JSON.pretty_generate(result)
128
+ end
129
+
130
+ puts
131
+
132
+ # Clean up direct client
133
+ client.disconnect
134
+ puts "Disconnected from direct MCP client."
135
+
136
+ # ============================================================================
137
+ # Part 2: Robot + MCP Integration
138
+ # ============================================================================
139
+
140
+ puts
141
+ puts "=" * 40
142
+ puts "PART 2: Robot + MCP Integration"
143
+ puts "-" * 40
144
+ puts
145
+
146
+ puts "Creating Robot with MCP server integration..."
147
+ puts
148
+
149
+ # Create a Robot with MCP server - tools are automatically discovered
150
+ robot = RobotLab.build(
151
+ name: "github_assistant",
152
+ template: :github_assistant,
153
+ mcp_servers: [github_server],
154
+ model: "claude-sonnet-4-20250514"
155
+ )
156
+
157
+ puts "Robot created: #{robot.name}"
158
+ puts " Model: #{robot.model}"
159
+ puts " MCP Servers: #{robot.mcp_clients.keys.join(", ")}"
160
+ puts " MCP Tools discovered: #{robot.mcp_tools.size}"
161
+ puts
162
+
163
+ # Show discovered MCP tools
164
+ puts "Discovered MCP Tools:"
165
+ puts "-" * 40
166
+ robot.mcp_tools.first(10).each do |tool|
167
+ puts " #{tool.name}"
168
+ puts " #{tool.description&.slice(0, 60)}..." if tool.description
169
+ end
170
+ puts " ... and #{robot.mcp_tools.size - 10} more" if robot.mcp_tools.size > 10
171
+ puts
172
+
173
+ # Run the robot with a query that will use MCP tools
174
+ puts "Running Robot with a GitHub query..."
175
+ puts "Query: 'What are the top 3 most starred Ruby web frameworks on GitHub?'"
176
+ puts "-" * 40
177
+
178
+ result = robot.run(message: "What are the top 3 most starred Ruby web frameworks on GitHub? Just list their names and star counts.")
179
+
180
+ puts
181
+ puts "Robot Response:"
182
+ puts "-" * 40
183
+ result.output.each do |msg|
184
+ puts msg.content if msg.respond_to?(:content)
185
+ end
186
+ puts
187
+
188
+ # Show tool calls if any were made
189
+ if result.tool_calls.any?
190
+ puts "Tool Calls Made:"
191
+ puts "-" * 40
192
+ result.tool_calls.each do |tc|
193
+ tool_info = tc.respond_to?(:tool) ? tc.tool : tc
194
+ puts " #{tool_info[:name] || tool_info}"
195
+ end
196
+ puts
197
+ end
198
+
199
+ # Clean up robot's MCP connections
200
+ robot.disconnect
201
+ puts "Robot MCP connections disconnected."
202
+
203
+ rescue RobotLab::MCPError => e
204
+ puts "MCP Error: #{e.message}"
205
+ exit 1
206
+ rescue Errno::ENOENT
207
+ puts <<~ERROR
208
+
209
+ ERROR: Could not find the github-mcp-server command.
210
+
211
+ Install it with:
212
+ brew install github-mcp-server
213
+ ERROR
214
+ exit 1
215
+ end
216
+
217
+ puts
218
+ puts "=" * 40
219
+ puts "Example complete!"