-
-
Notifications
You must be signed in to change notification settings - Fork 106
Expand file tree
/
Copy pathtool_examples.rb
More file actions
390 lines (327 loc) · 12.8 KB
/
Copy pathtool_examples.rb
File metadata and controls
390 lines (327 loc) · 12.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
# frozen_string_literal: true
#
# MCP Tool Examples
# ================
#
# This file demonstrates the different ways to define and use MCP tools.
# FastMcp::Tool is designed to be used as a base class that you inherit from to define your tools.
#
# Key concepts demonstrated:
#
# 1. Creating a tool by inheriting from FastMcp::Tool
# 2. Defining a schema using Dry::Schema to validate inputs
# 3. Using the arguments DSL to define input schemas
# 4. Validating arguments with various predicates (type checking, format checking, etc.)
# 5. Processing nested object structures
# 6. Handling optional arguments
# 7. Using call_with_schema_validation! to ensure inputs match the schema
#
# The preferred way to create tools is to inherit from FastMcp::Tool and define
# the arguments and call method in your subclass.
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
require 'fast_mcp'
puts 'MCP Tool Examples'
puts '================='
puts
# Example 1: Class-based with arguments using Dry::Schema
puts 'Example 1: Class-based with arguments using Dry::Schema'
class GreetTool < FastMcp::Tool
description 'Greet someone by name'
arguments do
required(:name).filled(:string).description('Name to greet')
end
def call(args)
"Hello, #{args[:name]}!"
end
end
# Create an instance and use it
greet_tool = GreetTool.new
puts "Tool name: #{greet_tool.class.name.split('::').last.gsub(/Tool$/, '').downcase}"
puts "Tool description: #{greet_tool.class.description}"
puts "Tool schema: #{greet_tool.class.input_schema.inspect}"
puts "Tool call result: #{greet_tool.call(name: 'World')}"
puts
# Example 2: Class with multiple arguments and validations
puts 'Example 2: Class with multiple arguments and validations'
class FullNameGreetTool < FastMcp::Tool
description 'Greet someone by their full name'
arguments do
required(:first_name).filled(:string, min_size?: 2).description('First name')
required(:last_name).filled(:string, format?: /^[A-Z][a-z]+$/).description('Last name')
end
def call(args)
"Hello, #{args[:first_name]} #{args[:last_name]}!"
end
end
full_name_tool = FullNameGreetTool.new
puts "Tool name: #{full_name_tool.class.name.split('::').last.gsub(/Tool$/, '').downcase}"
puts "Tool description: #{full_name_tool.class.description}"
puts "Tool schema: #{full_name_tool.class.input_schema.inspect}"
puts "Tool call result: #{full_name_tool.call_with_schema_validation!(first_name: 'John', last_name: 'Doe')}"
puts
# Example 3: Array arguments
puts 'Example 3: Array arguments'
class GroupGreetingTool < FastMcp::Tool
description 'Greet multiple people at once'
arguments do
required(:people).filled(:array, min_size?: 1).each(:string)
end
def call(args)
"Hello, #{args[:people].join(', ')}!"
end
end
group_tool = GroupGreetingTool.new
puts "Tool name: #{group_tool.class.name.split('::').last.gsub(/Tool$/, '').downcase}"
puts "Tool description: #{group_tool.class.description}"
puts "Tool schema: #{group_tool.class.input_schema.inspect}"
puts "Tool call result: #{group_tool.call_with_schema_validation!(people: %w[Alice Bob Charlie])}"
puts
# Example 4: Calculator with multiple argument types
puts 'Example 4: Calculator with multiple argument types'
class CalculatorTool < FastMcp::Tool
description 'Perform a calculation'
arguments do
required(:x).filled(:integer, gteq?: 0).description('First number')
required(:y).filled(:integer, gteq?: 0).description('Second number')
required(:operation).filled(:string,
included_in?: %w[add subtract multiply divide]).description('Operation to perform')
end
def call(args)
x = args[:x]
y = args[:y]
case args[:operation]
when 'add'
x + y
when 'subtract'
x - y
when 'multiply'
x * y
when 'divide'
x.to_f / y
end
end
end
calculator_tool = CalculatorTool.new
puts "Tool name: #{calculator_tool.class.name.split('::').last.gsub(/Tool$/, '').downcase}"
puts "Tool description: #{calculator_tool.class.description}"
puts "Tool schema: #{calculator_tool.class.input_schema.inspect}"
puts "Tool call result: #{calculator_tool.call_with_schema_validation!(x: 10, y: 5, operation: 'multiply')}"
puts
# Example 5: Nested object structures
puts 'Example 5: Nested object structures'
class UserValidatorTool < FastMcp::Tool
description 'Validate user information'
arguments do
required(:user).hash do
required(:username).filled(:string, min_size?: 3, max_size?: 20,
format?: /^[a-zA-Z0-9_]+$/).description('Username')
required(:email).filled(:string).description('Email address')
required(:age).filled(:integer, gt?: 18, lt?: 120).description('Age in years')
required(:interests).array(:string).description('User interests')
end
end
def call(args)
user = args[:user]
"User #{user[:username]} (#{user[:email]}) is #{user[:age]} years old and likes #{user[:interests].join(', ')}."
end
end
user_validator = UserValidatorTool.new
puts "Tool name: #{user_validator.class.name.split('::').last.gsub(/Tool$/, '').downcase}"
puts "Tool description: #{user_validator.class.description}"
puts "Tool schema: #{user_validator.class.input_schema.inspect}"
puts "Tool call result: #{user_validator.call_with_schema_validation!(
user: {
username: 'johndoe',
email: 'john@example.com',
age: 30,
interests: %w[coding reading]
}
)}"
puts
# Example 6: Optional arguments
puts 'Example 6: Optional arguments'
class UserProfileTool < FastMcp::Tool
description 'Create a user profile with optional fields'
arguments do
required(:username).filled(:string, min_size?: 3, max_size?: 20, format?: /^[a-zA-Z0-9_]+$/).description('Username')
required(:email).filled(:string).description('Email address')
required(:age).filled(:integer, gt?: 18, lt?: 120).description('Age in years')
optional(:interests).array(:string).description('User interests')
end
def call(args)
interests_text = args[:interests] ? args[:interests].join(', ') : 'nothing in particular'
"User profile created: #{args[:username]} (#{args[:email]}) is #{args[:age]} years old and likes #{interests_text}."
end
end
user_profile = UserProfileTool.new
puts "Tool name: #{user_profile.class.name.split('::').last.gsub(/Tool$/, '').downcase}"
puts "Tool description: #{user_profile.class.description}"
puts "Tool schema: #{user_profile.class.input_schema.inspect}"
puts "Tool call result with interests: #{user_profile.call_with_schema_validation!(
username: 'johndoe',
email: 'john@example.com',
age: 30,
interests: %w[coding reading]
)}"
puts "Tool call result without interests: #{user_profile.call_with_schema_validation!(
username: 'janedoe',
email: 'jane@example.com',
age: 25
)}"
puts
# Example 7: Tools with descriptions and hidden properties
puts 'Example 7: Tools with descriptions and hidden properties'
class ApiCallTool < FastMcp::Tool
description 'Make an authenticated API call'
arguments do
required(:url).filled(:string).description('API endpoint URL')
required(:method).filled(:string, included_in?: %w[GET POST PUT DELETE]).description('HTTP method')
optional(:headers).hash.description('HTTP headers to include')
optional(:api_key).filled(:string).description('API key for authentication').hidden(true)
optional(:secret_token).filled(:string).hidden(true) # Hidden field without description
end
def call(args)
headers_info = args[:headers] ? 'with custom headers' : 'with default headers'
auth_info = args[:api_key] ? 'authenticated' : 'unauthenticated'
"Making #{auth_info} #{args[:method]} request to #{args[:url]} #{headers_info}"
end
end
api_tool = ApiCallTool.new
puts "Tool: #{api_tool.class.name}"
puts 'JSON Schema:'
require 'json'
puts JSON.pretty_generate(api_tool.class.input_schema_to_json)
puts "Call result: #{api_tool.call(url: 'https://api.example.com/users', method: 'GET', api_key: 'secret123')}"
puts
class DatabaseQueryTool < FastMcp::Tool
description 'Execute a database query with connection settings'
arguments do
required(:query).filled(:string).description('SQL query to execute')
required(:connection).description('Database connection settings').hash do
required(:host).filled(:string).description('Database host')
required(:port).filled(:integer, gteq?: 1, lteq?: 65_535).description('Database port')
required(:database).filled(:string).description('Database name')
required(:username).filled(:string).description('Database username')
required(:password).filled(:string).hidden(true) # Hidden password
optional(:ssl_mode).filled(:string, included_in?: %w[disable require prefer]).description('SSL connection mode')
optional(:internal_id).filled(:string).hidden(true) # Hidden internal field
end
optional(:timeout).filled(:integer, gteq?: 1).description('Query timeout in seconds')
optional(:debug_info).filled(:string).hidden(true) # Hidden debug field
end
def call(args)
conn = args[:connection]
timeout_info = args[:timeout] ? "with #{args[:timeout]}s timeout" : 'with default timeout'
ssl_info = conn[:ssl_mode] ? "using #{conn[:ssl_mode]} SSL" : 'without SSL config'
"Executing query on #{conn[:database]}@#{conn[:host]}:#{conn[:port]} #{ssl_info} #{timeout_info}"
end
end
db_tool = DatabaseQueryTool.new
puts "Tool: #{db_tool.class.name}"
puts 'JSON Schema:'
puts JSON.pretty_generate(db_tool.class.input_schema_to_json)
puts "Call result: #{db_tool.call(
query: 'SELECT * FROM users',
connection: {
host: 'localhost',
port: 5432,
database: 'myapp',
username: 'admin',
password: 'secret123',
ssl_mode: 'require'
},
timeout: 30
)}"
puts
# Example 8: Tools with annotations
puts 'Example 8: Tools with annotations'
class WebSearchTool < FastMcp::Tool
description 'Search the web for information'
annotations(
title: 'Web Search',
read_only_hint: true,
open_world_hint: true
)
arguments do
required(:query).filled(:string).description('Search query')
optional(:max_results).filled(:integer, gteq?: 1, lteq?: 50).description('Maximum number of results')
end
def call(args)
max_results = args[:max_results] || 10
"Searching for '#{args[:query]}' (returning up to #{max_results} results)..."
end
end
class DeleteFileTool < FastMcp::Tool
description 'Delete a file from the filesystem'
annotations(
title: 'Delete File',
read_only_hint: false,
destructive_hint: true,
idempotent_hint: true,
open_world_hint: false
)
arguments do
required(:path).filled(:string).description('File path to delete')
end
def call(args)
"Would delete file at: #{args[:path]}"
end
end
class CreateRecordTool < FastMcp::Tool
description 'Create a new record in the database'
annotations(
title: 'Create Database Record',
read_only_hint: false,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false
)
arguments do
required(:table).filled(:string).description('Database table name')
required(:data).hash.description('Record data')
end
def call(args)
"Creating record in #{args[:table]} with data: #{args[:data].inspect}"
end
end
# Example 9: With an array of hashes as arguments
class BulkCreateUsersTool < FastMcp::Tool
description 'Create multiple user records in the database'
arguments do
required(:users).description('users to create').array(:hash) do
required(:username).filled(:string).description('Username')
required(:email).filled(:string).description('Email address')
optional(:age).filled(:integer, gt?: 0).description('Age in years')
optional(:address).hash.description('Address information') do
required(:street).filled(:string).description('Street address')
optional(:region).maybe(:string).description('Region')
end
end
end
def call(users:)
users.each_with_index.map do |user, index|
# Create a user record (simulated here)
end
end
end
bulk_create_tool = BulkCreateUsersTool.new
puts "Tool: #{bulk_create_tool.class.name}"
puts 'JSON Schema:'
puts JSON.pretty_generate(bulk_create_tool.class.input_schema_to_json)
# Demonstrate the web search tool
web_search = WebSearchTool.new
puts "Tool: #{web_search.class.name}"
puts "Annotations: #{web_search.class.annotations.inspect}"
puts "Call result: #{web_search.call(query: 'Ruby programming')}"
puts
# Demonstrate the delete file tool
delete_file = DeleteFileTool.new
puts "Tool: #{delete_file.class.name}"
puts "Annotations: #{delete_file.class.annotations.inspect}"
puts "Call result: #{delete_file.call(path: '/tmp/test.txt')}"
puts
# Demonstrate the create record tool
create_record = CreateRecordTool.new
puts "Tool: #{create_record.class.name}"
puts "Annotations: #{create_record.class.annotations.inspect}"
puts "Call result: #{create_record.call(table: 'users', data: { name: 'John', email: 'john@example.com' })}"