Tools are a core concept in the Model Context Protocol (MCP). They allow you to define functions that can be called by clients, including AI models. This guide covers everything you need to know about defining, using, and extending tools in Fast MCP.
MCP Tools are functions that can be called by clients with arguments and return results. They are defined on the server side and can be discovered and called by clients. Tools can:
- Perform calculations
- Access and modify resources
- Interact with external systems
- Call other tools
- Return structured data
Tools are particularly useful for AI models, as they provide a way for models to perform actions in the real world.
To define a tool, create a class that inherits from FastMcp::Tool
:
class HelloTool < FastMcp::Tool
description "Say hello to someone"
def call(**_args)
"Hello, world!"
end
end
# Create a server
server = FastMcp::Server.new(name: 'example-server', version: '1.0.0')
# Register the tool with the server
server.register_tool(HelloTool)
When defining a tool class, you can:
- Set a description using the
description
class method - Define arguments using the
arguments
class method with Dry::Schema - Implement the functionality in the
call
instance method
To define arguments for a tool, use the arguments
class method with a block using Dry::Schema syntax:
class GreetTool < FastMcp::Tool
description "Greet a person"
arguments do
required(:name).filled(:string).description("Name of the person")
end
def call(name:)
"Hello, #{name}!"
end
end
# Register the tool
server.register_tool(GreetTool)
The arguments
method takes a block where you can define:
- Required arguments using the
required
method - Optional arguments using the
optional
method - Types and validations for each argument
- Descriptions for each argument
Fast MCP supports the following argument types using Dry::Schema predicates:
:string
: A string value:integer
: An integer value:float
: A floating-point number:bool
: A boolean value (true/false):array
: An array of values:hash
: A hash/object with key-value pairs
Example with different types:
class ProcessDataTool < FastMcp::Tool
description "Process various types of data"
arguments do
required(:text).filled(:string).description("Text to process")
optional(:count).filled(:integer).description("Number of times to process")
optional(:factor).filled(:float).description("Multiplication factor")
optional(:verbose).filled(:bool).description("Whether to output verbose logs")
optional(:tags).array(:string).description("Tags to apply")
optional(:metadata).hash.description("Additional metadata")
end
def call(text:, count: 1, factor: 1.0, verbose: false, tags: [], metadata: {})
# Implementation
result = text * count
result = result * factor if factor != 1.0
if verbose
{
result: result,
tags: tags,
metadata: metadata
}
else
result
end
end
end
Fast MCP automatically validates arguments based on the Dry::Schema definition. If validation fails, an error is returned to the client.
You can also add custom validation in the call
method:
class DivideTool < FastMcp::Tool
description "Divide two numbers"
arguments do
required(:dividend).filled(:float).description("Number to be divided")
required(:divisor).filled(:float).description("Number to divide by")
end
def call(dividend:, divisor:)
# Custom validation
raise "Cannot divide by zero" if divisor == 0
dividend / divisor
end
end
You can specify default values in the method parameters of the call
method:
class RepeatTool < FastMcp::Tool
description "Repeat a string multiple times"
arguments do
required(:text).filled(:string).description("Text to repeat")
optional(:count).filled(:integer).description("Number of times to repeat")
end
def call(text:, count: 3)
text * count
end
end
To call a tool from a client:
client = FastMcp::Client.new(name: 'example-client', version: '1.0.0')
client.connect('ruby server.rb')
# Call a tool with arguments
result = client.call_tool('greet', { name: 'Alice' })
puts result # Outputs: Hello, Alice!
# Call a tool with no arguments
result = client.call_tool('hello')
puts result # Outputs: Hello, world!
Tools can call other tools through the server instance:
class GreetMultipleTool < FastMcp::Tool
description "Greet multiple people"
# Class variable to hold server instance
@server = nil
# Class methods to get and set server instance
class << self
attr_accessor :server
end
arguments do
required(:names).array(:string).description("Names of people to greet")
end
def call(names:)
raise "Server not set" unless self.class.server
results = names.map do |name|
# Get the tool instance
greet_tool = self.class.server.tools["greet"].new
# Call the tool
greet_tool.call(name: name)
end
results.join("\n")
end
end
# Set the server reference
GreetMultipleTool.server = server
# Register the tool
server.register_tool(GreetMultipleTool)
You can organize tools into categories using instance variables or metadata:
class AddTool < FastMcp::Tool
description "Add two numbers"
class << self
attr_accessor :category
end
self.category = "Math"
arguments do
required(:a).filled(:float).description("First number")
required(:b).filled(:float).description("Second number")
end
def call(a:, b:)
a + b
end
end
class SubtractTool < FastMcp::Tool
description "Subtract two numbers"
class << self
attr_accessor :category
end
self.category = "Math"
arguments do
required(:a).filled(:float).description("First number")
required(:b).filled(:float).description("Second number")
end
def call(a:, b:)
a - b
end
end
You can add metadata to tools using class methods:
class WeatherTool < FastMcp::Tool
description "Get the weather for a location"
class << self
attr_accessor :metadata
end
self.metadata = {
author: "John Doe",
version: "1.0.0",
tags: ["weather", "forecast"]
}
arguments do
required(:location).filled(:string).description("Location to get weather for")
end
def call(location:)
# Implementation
{ location: location, temperature: rand(0..30), condition: ["Sunny", "Cloudy", "Rainy"].sample }
end
end
You can implement permission checks:
class AdminActionTool < FastMcp::Tool
description "Perform an admin action"
class << self
attr_accessor :required_permission
end
self.required_permission = :admin
arguments do
required(:action).filled(:string).description("Action to perform")
required(:user_role).filled(:string).description("Role of the user making the request")
end
def call(action:, user_role:)
# Check permissions
raise "Permission denied: admin role required" unless user_role == "admin"
# Perform the action
"Admin action '#{action}' performed successfully"
end
end
Here are some best practices for working with MCP tools:
- Use Clear Names: Give your tools clear, descriptive names that indicate their purpose.
- Provide Good Descriptions: Write detailed descriptions for tools and their arguments.
- Validate Inputs: Use the schema validation to ensure inputs are correct before processing.
- Handle Errors Gracefully: Catch and handle errors properly, providing clear error messages.
- Return Structured Data: Return structured data when appropriate, especially for complex results.
- Test Your Tools: Write tests for your tools to ensure they work correctly.
- Document Usage: Document how to use your tools, including examples.
- Keep Tools Focused: Each tool should do one thing well, rather than trying to do too much.
Here's a more complex example of a tool that interacts with resources:
class IncrementCounterTool < FastMcp::Tool
description "Increment a counter resource"
# Class variable to hold server instance
@server = nil
# Class methods to get and set server instance
class << self
attr_accessor :server
end
arguments do
optional(:amount).filled(:integer).description("Amount to increment by")
end
def call(amount: 1)
raise "Server not set" unless self.class.server
# Get the counter resource
counter_resource = self.class.server.resources["counter"]
raise "Counter resource not found" unless counter_resource
# Parse the current value
current_value = counter_resource.content.to_i
# Increment the counter
new_value = current_value + amount
# Update the resource
counter_resource.update_content(new_value.to_s)
# Return the new value
{ previous_value: current_value, new_value: new_value, amount: amount }
end
end
# Set the server reference
IncrementCounterTool.server = server
# Register the tool
server.register_tool(IncrementCounterTool)
This tool increments a counter resource by a specified amount (or by 1 by default) and returns the previous and new values.