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
515Install 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
1424gem 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