Tsikol - Ruby MCP Framework
A elegant and easy to use framework for building Model Context Protocol (MCP) servers in Ruby.
Features
- ๐ Rails-like Structure - Familiar project organization with
app/tools
,app/resources
,app/prompts
- ๐ง Flexible DSL - Define components inline or in separate files
- ๐ Full MCP Protocol Support - Tools, Resources, Prompts, Logging, Completion, Sampling
- ๐ก๏ธ Middleware System - Add cross-cutting concerns like auth, rate limiting, logging
- ๐ Built-in Monitoring - Health checks, metrics, error tracking
- ๐งช Testing Utilities - Comprehensive test helpers and mock client
- โก Hot Reload - Auto-discovery of components
- ๐ Advanced Error Handling - Circuit breakers, error recovery, detailed logging
Installation
Add to your Gemfile:
gem 'tsikol'
Or install directly:
gem install tsikol
CLI Usage
Tsikol includes a powerful CLI tool for scaffolding projects and generating components.
Create a New Project
tsikol new my-mcp-server
cd my-mcp-server
bundle install
This creates a complete project structure with:
-
server.rb
- Main server file -
Gemfile
- Dependencies -
app/
directory structure for components - README and .gitignore
Generate Components
The CLI can generate tools, resources, and prompts with automatic server.rb updates:
Generate a Tool
# Basic tool
tsikol generate tool calculate
# Tool with parameters (name:type format)
tsikol generate tool calculate a:number b:number operation:string
# Optional parameters use ? suffix
tsikol generate tool weather_forecast location:string days?:integer units?:string
# Short alias
tsikol g tool my_tool param1:string param2:boolean
Generate a Resource
# Basic resource
tsikol generate resource system_status
# Resource with custom URI (auto-converts underscores to slashes)
tsikol generate resource user_profile # Creates URI: user/profile
# Short alias
tsikol g resource my_data
Generate a Prompt
# Basic prompt
tsikol generate prompt code_review
# Prompt with arguments
tsikol generate prompt code_review language:string code:string style?:string
# Short alias
tsikol g prompt chat_assistant topic:string context?:string
How the CLI Works
-
Generates the component file in the appropriate directory:
- Tools โ
app/tools/
- Resources โ
app/resources/
- Prompts โ
app/prompts/
- Tools โ
-
Updates server.rb automatically:
- Adds the require statement
- Registers the component in the routes
-
Smart insertion - Components are grouped by type in server.rb
Example Workflow
# Create a new weather service
tsikol new weather-service
cd weather-service
# Generate tools
tsikol g tool get_current_weather location:string units?:string
tsikol g tool get_forecast location:string days:integer
# Generate resources
tsikol g resource weather_alerts
tsikol g resource service_status
# Generate prompts
tsikol g prompt weather_chat city:string topic:string
# Your server.rb is automatically updated with all components!
./server.rb
Quick Start
Simple Server
#!/usr/bin/env ruby
require 'tsikol'
Tsikol.server "my-server" do
tool "greet" do |name:|
"Hello, #{name}!"
end
resource "status" do
"Server is running"
end
prompt "chat" do |topic:|
"Let's discuss #{topic}"
end
end
Rails-like Structure
# server.rb
require 'tsikol'
Tsikol.start(name: "weather-service") do
# Direct class references
tool GetCurrentWeather
tool GetForecast
resource WeatherAlerts
prompt WeatherChat
end
# app/tools/get_current_weather.rb
class GetCurrentWeather < Tsikol::Tool
description "Get current weather for a location"
parameter :location do
type :string
required
description "City name"
complete do |partial|
["New York", "London", "Tokyo"].select { |c| c.start_with?(partial) }
end
end
def execute(location:)
"Currently 72ยฐF in #{location}"
end
end
Core Concepts
Tools
Tools are functions that can be called by the MCP client:
# Inline definition
tool "calculate" do |a:, b:, operation: "add"|
case operation
when "add" then a + b
when "subtract" then a - b
when "multiply" then a * b
when "divide" then b.zero? ? "Error: Division by zero" : a.to_f / b
end
end
# Class-based definition
class Calculate < Tsikol::Tool
description "Perform calculations"
parameter :a do
type :number
required
description "First number"
end
parameter :b do
type :number
required
description "Second number"
end
parameter :operation do
type :string
optional
default "add"
enum ["add", "subtract", "multiply", "divide"]
end
def execute(a:, b:, operation: "add")
# Implementation
end
end
Resources
Resources provide data that can be read by the client:
# Inline definition
resource "config/database" do
{
host: ENV['DB_HOST'],
port: ENV['DB_PORT'],
name: ENV['DB_NAME']
}.to_json
end
# Class-based definition
class DatabaseConfig < Tsikol::Resource
uri "config/database"
description "Database configuration"
def read
# Return content
end
end
Prompts
Prompts generate messages for AI interactions:
# Inline definition
prompt "code_review" do |language:, code:|
"Review this #{language} code:\n```#{language}\n#{code}\n```"
end
# Class-based definition
class CodeReview < Tsikol::Prompt
name "code_review"
description "Generate code review prompt"
argument :language do
type :string
required
complete do |partial|
["ruby", "python", "javascript"].select { |l| l.start_with?(partial) }
end
end
def get_messages(language:, code:)
[{
role: "user",
content: {
type: "text",
text: "Review this #{language} code:\n```#{language}\n#{code}\n```"
}
}]
end
end
Completion (Autocomplete)
Provide intelligent autocomplete suggestions for tool parameters and prompt arguments:
# Tool with parameter completion
class SearchFiles < Tsikol::Tool
description "Search for files in the project"
parameter :file_type do
type :string
required
description "Type of file to search for"
complete do |partial|
# Return suggestions based on partial input
extensions = ["rb", "js", "py", "md", "yml", "json", "xml"]
extensions.select { |ext| ext.start_with?(partial) }
end
end
parameter :directory do
type :string
optional
description "Directory to search in"
complete do |partial|
# Dynamic completion based on filesystem
Dir.glob("#{partial}*").select { |f| File.directory?(f) }
end
end
def execute(file_type:, directory: ".")
Dir.glob("#{directory}/**/*.#{file_type}")
end
end
# Inline completion for tools
tool "database_query" do |table:, operation:|
"Executing #{operation} on #{table}"
end
# Define completions separately
completion_for "tool", "database_query", "table" do |partial|
# List available tables
["users", "posts", "comments", "tags"].select { |t| t.start_with?(partial) }
end
completion_for "tool", "database_query", "operation" do |partial|
["select", "insert", "update", "delete"].select { |op| op.start_with?(partial) }
end
Advanced Features
Middleware
Add cross-cutting concerns with middleware:
Tsikol.server "my-server" do
# Built-in middleware
use Tsikol::LoggingMiddleware
use Tsikol::RateLimitMiddleware, max_requests: 100, window: 60
use Tsikol::ValidationMiddleware
# Custom middleware
use AuthMiddleware, api_keys: ["secret-key"]
# Tools, resources, prompts...
end
Lifecycle Hooks
Tsikol.server "my-server" do
before_start do
log :info, "Initializing connections..."
# Setup code
end
after_start do
log :info, "Server ready!"
end
before_stop do
log :info, "Cleaning up..."
# Cleanup code
end
# Tool-specific hooks
before_tool "critical_operation" do |params|
log :warning, "About to run critical operation", data: params
end
after_tool "critical_operation" do |params, result|
log :info, "Critical operation completed", data: { result: result }
end
end
Sampling (AI Assistance)
Enable AI-assisted content generation where the MCP server can request help from the AI client:
Tsikol.server "my-server" do
# Enable sampling and define handler
on_sampling do |request|
# The request contains:
# - messages: Array of conversation messages
# - model_preferences: Hints about which model to use
# - system_prompt: Optional system instructions
# - max_tokens: Maximum response length
# In production, the MCP client (e.g., Claude) handles this
# For testing, you can simulate responses:
{
role: "assistant",
content: {
type: "text",
text: "AI-generated response based on: #{request[:messages].last}"
}
}
end
# Tool that uses AI assistance
tool "write_documentation" do |code:, style: "technical"|
# In a real MCP client, this would trigger a sampling request
prompt = case style
when "technical"
"Write technical documentation for this code"
when "tutorial"
"Write a beginner-friendly tutorial for this code"
when "api"
"Write API reference documentation"
end
# The actual AI response would come from the client
"#{prompt}:\n\n```ruby\n#{code}\n```"
end
# Advanced example with context
tool "refactor_code" do |code:, goal:|
# Build conversation for AI
messages = [
{
role: "system",
content: "You are an expert Ruby developer focused on clean code."
},
{
role: "user",
content: "Refactor this code to #{goal}:\n\n```ruby\n#{code}\n```"
}
]
# In production, this would send a sampling request
# with the messages to the MCP client
"Refactored code would appear here"
end
end
# Class-based tool with sampling
class CodeGenerator < Tsikol::Tool
description "Generate code using AI assistance"
parameter :description do
type :string
required
description "What code to generate"
end
parameter :language do
type :string
optional
default "ruby"
enum ["ruby", "python", "javascript", "go"]
end
def execute(description:, language: "ruby")
# Build sampling request
sampling_request = {
messages: [
{
role: "user",
content: {
type: "text",
text: "Generate #{language} code that: #{description}"
}
}
],
model_preferences: {
hints: [{ name: "claude-3-sonnet" }],
temperature: 0.7
},
max_tokens: 2000
}
# This would be sent to the MCP client
# Response would contain AI-generated code
"Generated #{language} code for: #{description}"
end
end
Health Monitoring
Built-in health endpoints are automatically added:
-
resource "health"
- Basic health status -
resource "health/detailed"
- Detailed health with metrics -
resource "metrics"
- Raw metrics data
Error Handling
Advanced error handling with circuit breakers:
tool "external_api" do
# Automatically wrapped with circuit breaker
# After 5 failures, circuit opens for 60 seconds
call_external_service
end
Testing
Tsikol includes comprehensive testing utilities:
require 'tsikol/test_helpers'
class MyServerTest < Minitest::Test
include Tsikol::TestHelpers::Assertions
def setup
@server = create_my_server
@client = Tsikol::TestHelpers::TestClient.new(@server)
end
def test_echo_tool
@client.initialize_connection
assert_tool_result(@client, "echo", { "msg" => "Hi" }, "Echo: Hi")
end
def test_error_handling
response = @client.call_tool("failing_tool")
assert_error_response(response, -32603, /Internal error/)
end
end
Project Structure
my-mcp-server/
โโโ server.rb # Main entry point
โโโ Gemfile
โโโ app/
โ โโโ tools/ # Tool classes
โ โโโ resources/ # Resource classes
โ โโโ prompts/ # Prompt classes
โโโ config/
โ โโโ tsikol.yml # Configuration (optional)
โโโ test/
โโโ server_test.rb # Tests
Examples
See the examples/
directory for complete examples:
-
basic.rb
- Simple inline server -
weather-service/
- Full Rails-like project -
middleware_example.rb
- Middleware demonstration -
sampling_example.rb
- AI assistance -
advanced_features.rb
- All features combined
License
MIT License - see LICENSE file for details.
Acknowledgments
Built for the Model Context Protocol (MCP) by Anthropic.