Skip to content

Commit d5355c4

Browse files
committed
Ready to go
1 parent b2e14a5 commit d5355c4

8 files changed

Lines changed: 819 additions & 3 deletions

File tree

.rubocop.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ Lint/UnusedMethodArgument:
1515
Metrics/AbcSize:
1616
Enabled: false
1717

18+
Metrics/BlockLength:
19+
Enabled: false
20+
1821
Metrics/CyclomaticComplexity:
1922
Enabled: false
2023

README.md

Lines changed: 316 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# ToolForge
22

3+
ToolForge is a Ruby gem that provides a unified DSL for defining tools that can be converted to both [RubyLLM](https://github.com/ruby-llm/ruby-llm) and [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) formats. Write your tool once, use it anywhere.
4+
5+
## Features
6+
7+
- 🎯 **Unified DSL**: Define tools once, convert to multiple formats
8+
- 🔧 **Helper Methods**: Support for both instance and class helper methods
9+
- 📊 **Type Safety**: Parameter validation and type conversion
10+
- 🚀 **Framework Agnostic**: Works with RubyLLM and MCP frameworks
11+
- 📝 **Clean API**: Intuitive, Ruby-like syntax
12+
313
## Installation
414

515
Install the gem and add to the application's Gemfile by executing:
@@ -14,9 +24,313 @@ If bundler is not being used to manage dependencies, install the gem by executin
1424
gem install tool_forge
1525
```
1626

17-
## Usage
27+
## Quick Start
28+
29+
```ruby
30+
require 'tool_forge'
31+
32+
# Define a tool
33+
tool = ToolForge.define(:greet_user) do
34+
description 'Greets a user with a personalized message'
35+
36+
param :name, type: :string, description: 'User name'
37+
param :greeting, type: :string, description: 'Greeting style',
38+
required: false, default: 'Hello'
39+
40+
execute do |name:, greeting:|
41+
"#{greeting}, #{name}! Welcome to ToolForge!"
42+
end
43+
end
44+
45+
# Convert to RubyLLM format
46+
ruby_llm_tool = tool.to_ruby_llm_tool
47+
instance = ruby_llm_tool.new
48+
result = instance.execute(name: 'Alice')
49+
#=> "Hello, Alice! Welcome to ToolForge!"
50+
51+
# Convert to MCP format
52+
mcp_tool = tool.to_mcp_tool
53+
result = mcp_tool.call(server_context: nil, name: 'Bob')
54+
#=> Returns MCP::Tool::Response object
55+
```
56+
57+
## Detailed Usage
58+
59+
### Basic Tool Definition
60+
61+
```ruby
62+
tool = ToolForge.define(:file_reader) do
63+
description 'Reads and processes files'
64+
65+
# Define parameters with types and validation
66+
param :filename, type: :string, description: 'Path to file'
67+
param :encoding, type: :string, required: false, default: 'utf-8'
68+
param :max_lines, type: :integer, required: false
69+
70+
# Define execution logic
71+
execute do |filename:, encoding:, max_lines:|
72+
content = File.read(filename, encoding: encoding)
73+
lines = content.lines
74+
75+
if max_lines
76+
lines.first(max_lines).join
77+
else
78+
content
79+
end
80+
end
81+
end
82+
```
83+
84+
### Helper Methods
85+
86+
ToolForge supports two types of helper methods:
87+
88+
#### Instance Helper Methods
89+
90+
Use `helper` for methods that operate on instance data:
91+
92+
```ruby
93+
tool = ToolForge.define(:text_processor) do
94+
description 'Processes text with formatting'
95+
96+
param :text, type: :string
97+
param :format, type: :string, default: 'uppercase'
98+
99+
# Instance helper method
100+
helper(:format_text) do |text, format|
101+
case format
102+
when 'uppercase' then text.upcase
103+
when 'lowercase' then text.downcase
104+
when 'title' then text.split.map(&:capitalize).join(' ')
105+
else text
106+
end
107+
end
108+
109+
helper(:add_prefix) do |text|
110+
"PROCESSED: #{text}"
111+
end
112+
113+
execute do |text:, format:|
114+
formatted = format_text(text, format)
115+
add_prefix(formatted)
116+
end
117+
end
118+
```
119+
120+
#### Class Helper Methods
121+
122+
Use `class_helper` for utility methods that don't depend on instance state:
123+
124+
```ruby
125+
tool = ToolForge.define(:docker_copy) do
126+
description 'Copies files to Docker containers'
127+
128+
param :container_id, type: :string
129+
param :source_path, type: :string
130+
param :dest_path, type: :string
131+
132+
# Class helper method - useful for utilities
133+
class_helper(:add_to_tar) do |file_path, tar_path|
134+
# Implementation for tar operations
135+
"Added #{file_path} to tar archive as #{tar_path}"
136+
end
137+
138+
class_helper(:validate_container) do |container_id|
139+
# Validation logic
140+
container_id.match?(/^[a-f0-9]{12}$/)
141+
end
142+
143+
execute do |container_id:, source_path:, dest_path:|
144+
return "Invalid container ID" unless self.class.validate_container(container_id)
145+
146+
tar_result = self.class.add_to_tar(source_path, dest_path)
147+
"Copied #{source_path} to #{container_id}:#{dest_path} - #{tar_result}"
148+
end
149+
end
150+
```
151+
152+
### Parameter Types
153+
154+
ToolForge supports various parameter types:
155+
156+
```ruby
157+
tool = ToolForge.define(:complex_tool) do
158+
param :name, type: :string # String parameter
159+
param :count, type: :integer # Integer parameter
160+
param :active, type: :boolean # Boolean parameter
161+
param :rate, type: :number # Numeric parameter
162+
param :tags, type: :array # Array parameter
163+
param :metadata, type: :object # Object/Hash parameter
164+
param :config, type: :string, required: false, default: 'default.json'
165+
166+
execute do |**params|
167+
# Access all parameters
168+
params.inspect
169+
end
170+
end
171+
```
172+
173+
### Framework Integration
174+
175+
#### RubyLLM Integration
176+
177+
```ruby
178+
require 'ruby_llm'
179+
require 'tool_forge'
180+
181+
tool = ToolForge.define(:my_tool) do
182+
# ... tool definition
183+
end
184+
185+
# Convert to RubyLLM format
186+
ruby_llm_class = tool.to_ruby_llm_tool
187+
188+
# Use with RubyLLM
189+
llm = RubyLLM::Client.new
190+
llm.add_tool(ruby_llm_class)
191+
```
192+
193+
#### MCP Integration
194+
195+
```ruby
196+
require 'mcp'
197+
require 'tool_forge'
198+
199+
tool = ToolForge.define(:my_tool) do
200+
# ... tool definition
201+
end
202+
203+
# Convert to MCP format
204+
mcp_class = tool.to_mcp_tool
205+
206+
# Use with MCP server
207+
server = MCP::Server.new
208+
server.add_tool(mcp_class)
209+
```
210+
211+
## Advanced Features
212+
213+
### Complex Data Processing
214+
215+
```ruby
216+
tool = ToolForge.define(:data_analyzer) do
217+
description 'Analyzes data files and generates reports'
218+
219+
param :files, type: :array, description: 'List of file paths'
220+
param :output_format, type: :string, default: 'json'
221+
222+
helper(:read_file_data) do |file_path|
223+
return { error: "File not found: #{file_path}" } unless File.exist?(file_path)
224+
225+
{
226+
path: file_path,
227+
size: File.size(file_path),
228+
lines: File.readlines(file_path).count,
229+
modified: File.mtime(file_path)
230+
}
231+
end
232+
233+
helper(:format_output) do |data, format|
234+
case format
235+
when 'json' then JSON.pretty_generate(data)
236+
when 'yaml' then data.to_yaml
237+
when 'csv' then data.map { |row| row.values.join(',') }.join("\n")
238+
else data.inspect
239+
end
240+
end
241+
242+
execute do |files:, output_format:|
243+
results = files.map { |file| read_file_data(file) }
244+
245+
summary = {
246+
total_files: results.count,
247+
total_size: results.sum { |r| r[:size] || 0 },
248+
files: results
249+
}
250+
251+
format_output(summary, output_format)
252+
end
253+
end
254+
```
255+
256+
### Error Handling
257+
258+
```ruby
259+
tool = ToolForge.define(:safe_processor) do
260+
description 'Processes data with comprehensive error handling'
261+
262+
param :input, type: :string
263+
param :operation, type: :string
264+
265+
helper(:validate_input) do |input|
266+
raise ArgumentError, "Input cannot be empty" if input.nil? || input.empty?
267+
raise ArgumentError, "Input too long" if input.length > 1000
268+
true
269+
end
270+
271+
execute do |input:, operation:|
272+
begin
273+
validate_input(input)
274+
275+
case operation
276+
when 'reverse' then input.reverse
277+
when 'upcase' then input.upcase
278+
when 'analyze' then { length: input.length, words: input.split.count }
279+
else
280+
{ error: "Unknown operation: #{operation}" }
281+
end
282+
rescue => e
283+
{ error: e.message }
284+
end
285+
end
286+
end
287+
```
288+
289+
## API Reference
290+
291+
### ToolForge.define(name, &block)
292+
293+
Creates a new tool definition.
294+
295+
- `name` (Symbol): The tool name
296+
- `block`: Configuration block using the DSL
297+
298+
### DSL Methods
299+
300+
#### `description(text)`
301+
Sets the tool description.
302+
303+
#### `param(name, options = {})`
304+
Defines a parameter with options:
305+
- `type`: Parameter type (`:string`, `:integer`, `:boolean`, `:number`, `:array`, `:object`)
306+
- `description`: Parameter description
307+
- `required`: Whether required (default: `true`)
308+
- `default`: Default value for optional parameters
309+
310+
#### `helper(name, &block)`
311+
Defines an instance helper method.
312+
313+
#### `class_helper(name, &block)`
314+
Defines a class helper method.
315+
316+
#### `execute(&block)`
317+
Defines the tool execution logic.
318+
319+
### Conversion Methods
320+
321+
#### `#to_ruby_llm_tool`
322+
Converts to a RubyLLM::Tool class.
323+
324+
#### `#to_mcp_tool`
325+
Converts to an MCP::Tool class.
326+
327+
## Examples
18328

19-
TODO: Write usage instructions here
329+
See the [examples directory](examples/) for more comprehensive examples including:
330+
- File processing tools
331+
- API integration tools
332+
- Data transformation tools
333+
- Docker management tools
20334

21335
## Development
22336

0 commit comments

Comments
 (0)