tsikol 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +22 -0
- data/CONTRIBUTING.md +84 -0
- data/LICENSE +21 -0
- data/README.md +579 -0
- data/Rakefile +12 -0
- data/docs/README.md +69 -0
- data/docs/api/middleware.md +721 -0
- data/docs/api/prompt.md +858 -0
- data/docs/api/resource.md +651 -0
- data/docs/api/server.md +509 -0
- data/docs/api/test-helpers.md +591 -0
- data/docs/api/tool.md +527 -0
- data/docs/cookbook/authentication.md +651 -0
- data/docs/cookbook/caching.md +877 -0
- data/docs/cookbook/dynamic-tools.md +970 -0
- data/docs/cookbook/error-handling.md +887 -0
- data/docs/cookbook/logging.md +1044 -0
- data/docs/cookbook/rate-limiting.md +717 -0
- data/docs/examples/code-assistant.md +922 -0
- data/docs/examples/complete-server.md +726 -0
- data/docs/examples/database-manager.md +1198 -0
- data/docs/examples/devops-tools.md +1382 -0
- data/docs/examples/echo-server.md +501 -0
- data/docs/examples/weather-service.md +822 -0
- data/docs/guides/completion.md +472 -0
- data/docs/guides/getting-started.md +462 -0
- data/docs/guides/middleware.md +823 -0
- data/docs/guides/project-structure.md +434 -0
- data/docs/guides/prompts.md +920 -0
- data/docs/guides/resources.md +720 -0
- data/docs/guides/sampling.md +804 -0
- data/docs/guides/testing.md +863 -0
- data/docs/guides/tools.md +627 -0
- data/examples/README.md +92 -0
- data/examples/advanced_features.rb +129 -0
- data/examples/basic-migrated/app/prompts/weather_chat.rb +44 -0
- data/examples/basic-migrated/app/resources/weather_alerts.rb +18 -0
- data/examples/basic-migrated/app/tools/get_current_weather.rb +34 -0
- data/examples/basic-migrated/app/tools/get_forecast.rb +30 -0
- data/examples/basic-migrated/app/tools/get_weather_by_coords.rb +48 -0
- data/examples/basic-migrated/server.rb +25 -0
- data/examples/basic.rb +73 -0
- data/examples/full_featured.rb +175 -0
- data/examples/middleware_example.rb +112 -0
- data/examples/sampling_example.rb +104 -0
- data/examples/weather-service/app/prompts/weather/chat.rb +90 -0
- data/examples/weather-service/app/resources/weather/alerts.rb +59 -0
- data/examples/weather-service/app/tools/weather/get_current.rb +82 -0
- data/examples/weather-service/app/tools/weather/get_forecast.rb +90 -0
- data/examples/weather-service/server.rb +28 -0
- data/exe/tsikol +6 -0
- data/lib/tsikol/cli/templates/Gemfile.erb +10 -0
- data/lib/tsikol/cli/templates/README.md.erb +38 -0
- data/lib/tsikol/cli/templates/gitignore.erb +49 -0
- data/lib/tsikol/cli/templates/prompt.rb.erb +53 -0
- data/lib/tsikol/cli/templates/resource.rb.erb +29 -0
- data/lib/tsikol/cli/templates/server.rb.erb +24 -0
- data/lib/tsikol/cli/templates/tool.rb.erb +60 -0
- data/lib/tsikol/cli.rb +203 -0
- data/lib/tsikol/error_handler.rb +141 -0
- data/lib/tsikol/health.rb +198 -0
- data/lib/tsikol/http_transport.rb +72 -0
- data/lib/tsikol/lifecycle.rb +149 -0
- data/lib/tsikol/middleware.rb +168 -0
- data/lib/tsikol/prompt.rb +101 -0
- data/lib/tsikol/resource.rb +53 -0
- data/lib/tsikol/router.rb +190 -0
- data/lib/tsikol/server.rb +660 -0
- data/lib/tsikol/stdio_transport.rb +108 -0
- data/lib/tsikol/test_helpers.rb +261 -0
- data/lib/tsikol/tool.rb +111 -0
- data/lib/tsikol/version.rb +5 -0
- data/lib/tsikol.rb +72 -0
- metadata +219 -0
@@ -0,0 +1,104 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative '../lib/tsikol'
|
5
|
+
|
6
|
+
Tsikol.server "ai-assistant" do
|
7
|
+
# Enable and configure sampling
|
8
|
+
on_sampling do |request|
|
9
|
+
# This handler is called when the server needs AI assistance
|
10
|
+
# In a real implementation, this would be handled by the MCP client
|
11
|
+
# For demo purposes, we'll simulate a response
|
12
|
+
|
13
|
+
messages = request[:messages]
|
14
|
+
system_prompt = request[:system_prompt]
|
15
|
+
max_tokens = request[:max_tokens] || 100
|
16
|
+
|
17
|
+
log :info, "Sampling request received", data: {
|
18
|
+
message_count: messages.size,
|
19
|
+
system_prompt: system_prompt,
|
20
|
+
max_tokens: max_tokens
|
21
|
+
}
|
22
|
+
|
23
|
+
# In real usage, the MCP client (like Claude Code) would:
|
24
|
+
# 1. Show the request to the user for approval
|
25
|
+
# 2. Send to the AI model
|
26
|
+
# 3. Return the response
|
27
|
+
|
28
|
+
# For demo, return a simulated response
|
29
|
+
{
|
30
|
+
role: "assistant",
|
31
|
+
content: {
|
32
|
+
type: "text",
|
33
|
+
text: "This is a simulated AI response. In production, the MCP client would handle this request and return actual AI-generated content."
|
34
|
+
}
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Tool that uses sampling to enhance its functionality
|
39
|
+
tool "enhance_text" do |text:, style: "professional"|
|
40
|
+
log :info, "Enhancing text with style: #{style}"
|
41
|
+
|
42
|
+
# Request AI assistance via sampling
|
43
|
+
begin
|
44
|
+
# In a real implementation, the server would send a sampling request
|
45
|
+
# to the client, which would then use an AI model
|
46
|
+
|
47
|
+
# For now, we'll just demonstrate the structure
|
48
|
+
enhanced = case style
|
49
|
+
when "professional"
|
50
|
+
"#{text} (enhanced professionally)"
|
51
|
+
when "casual"
|
52
|
+
"#{text} (made more casual)"
|
53
|
+
when "technical"
|
54
|
+
"#{text} (with technical details)"
|
55
|
+
else
|
56
|
+
text
|
57
|
+
end
|
58
|
+
|
59
|
+
"Enhanced: #{enhanced}"
|
60
|
+
rescue => e
|
61
|
+
log :error, "Enhancement failed", data: { error: e.message }
|
62
|
+
"Error enhancing text: #{e.message}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Tool that generates content using sampling
|
67
|
+
tool "generate_content" do |topic:, length: "short"|
|
68
|
+
log :info, "Generating content about: #{topic}"
|
69
|
+
|
70
|
+
# This would trigger a sampling request in a real implementation
|
71
|
+
content_length = case length
|
72
|
+
when "short"
|
73
|
+
"Brief content about #{topic}."
|
74
|
+
when "medium"
|
75
|
+
"Detailed explanation about #{topic} with examples and context."
|
76
|
+
when "long"
|
77
|
+
"Comprehensive analysis of #{topic} including background, current state, and future implications."
|
78
|
+
else
|
79
|
+
"Content about #{topic}."
|
80
|
+
end
|
81
|
+
|
82
|
+
content_length
|
83
|
+
end
|
84
|
+
|
85
|
+
# Resource that provides sampling configuration info
|
86
|
+
resource "sampling/config" do
|
87
|
+
{
|
88
|
+
enabled: true,
|
89
|
+
models_supported: ["claude-3-sonnet", "claude-3-opus"],
|
90
|
+
max_tokens_limit: 4096,
|
91
|
+
features: [
|
92
|
+
"Content generation",
|
93
|
+
"Text enhancement",
|
94
|
+
"Code generation",
|
95
|
+
"Analysis and reasoning"
|
96
|
+
]
|
97
|
+
}.to_json
|
98
|
+
end
|
99
|
+
|
100
|
+
# Prompt that can be enhanced with sampling
|
101
|
+
prompt "brainstorm" do |topic:, approach: "creative"|
|
102
|
+
"Help me brainstorm ideas about #{topic} using a #{approach} approach. Generate diverse and innovative suggestions."
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Weather
|
4
|
+
class Chat < Tsikol::Prompt
|
5
|
+
name "weather-chat"
|
6
|
+
description "Interactive weather information assistant"
|
7
|
+
|
8
|
+
argument :topic do
|
9
|
+
type :string
|
10
|
+
required
|
11
|
+
description "Weather topic to discuss"
|
12
|
+
|
13
|
+
complete do |partial|
|
14
|
+
topics = [
|
15
|
+
"current conditions",
|
16
|
+
"forecast",
|
17
|
+
"climate trends",
|
18
|
+
"severe weather",
|
19
|
+
"travel planning",
|
20
|
+
"outdoor activities",
|
21
|
+
"seasonal patterns",
|
22
|
+
"weather safety"
|
23
|
+
]
|
24
|
+
topics.select { |topic| topic.downcase.include?(partial.downcase) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
argument :location do
|
29
|
+
type :string
|
30
|
+
optional
|
31
|
+
description "Specific location for weather discussion"
|
32
|
+
|
33
|
+
complete do |partial|
|
34
|
+
cities = [
|
35
|
+
"New York", "London", "Tokyo", "Paris", "Berlin",
|
36
|
+
"Sydney", "Toronto", "Mumbai", "Beijing", "Moscow"
|
37
|
+
]
|
38
|
+
cities.select { |city| city.downcase.start_with?(partial.downcase) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_messages(topic:, location: nil)
|
43
|
+
base_prompt = case topic
|
44
|
+
when "current conditions"
|
45
|
+
"You are a weather assistant. Help me understand the current weather conditions#{location ? " in #{location}" : ""}."
|
46
|
+
when "forecast"
|
47
|
+
"You are a weather forecasting expert. Provide detailed forecast information#{location ? " for #{location}" : ""}."
|
48
|
+
when "climate trends"
|
49
|
+
"You are a climate scientist. Discuss recent climate trends and patterns#{location ? " affecting #{location}" : ""}."
|
50
|
+
when "severe weather"
|
51
|
+
"You are a severe weather specialist. Explain severe weather risks and safety#{location ? " for #{location}" : ""}."
|
52
|
+
when "travel planning"
|
53
|
+
"You are a travel weather advisor. Help plan travel considering weather conditions#{location ? " for #{location}" : ""}."
|
54
|
+
when "outdoor activities"
|
55
|
+
"You are an outdoor activity weather consultant. Advise on weather for outdoor activities#{location ? " in #{location}" : ""}."
|
56
|
+
when "seasonal patterns"
|
57
|
+
"You are a seasonal weather expert. Explain seasonal weather patterns#{location ? " for #{location}" : ""}."
|
58
|
+
when "weather safety"
|
59
|
+
"You are a weather safety expert. Provide weather safety tips and guidelines#{location ? " for #{location}" : ""}."
|
60
|
+
else
|
61
|
+
"You are a knowledgeable weather assistant. Help me understand #{topic}#{location ? " for #{location}" : ""}."
|
62
|
+
end
|
63
|
+
|
64
|
+
[
|
65
|
+
{
|
66
|
+
role: "system",
|
67
|
+
content: {
|
68
|
+
type: "text",
|
69
|
+
text: base_prompt + " Provide accurate, helpful information based on typical weather patterns and safety guidelines."
|
70
|
+
}
|
71
|
+
},
|
72
|
+
{
|
73
|
+
role: "user",
|
74
|
+
content: {
|
75
|
+
type: "text",
|
76
|
+
text: "I'd like to know about #{topic}#{location ? " in #{location}" : ""}. What can you tell me?"
|
77
|
+
}
|
78
|
+
}
|
79
|
+
]
|
80
|
+
end
|
81
|
+
|
82
|
+
def set_server(server)
|
83
|
+
@server = server
|
84
|
+
|
85
|
+
define_singleton_method(:log) do |level, message, data: nil, logger: nil|
|
86
|
+
@server.log(level, message, data: data, logger: logger)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Weather
|
4
|
+
class Alerts < Tsikol::Resource
|
5
|
+
uri "weather/alerts"
|
6
|
+
description "Active weather alerts and warnings"
|
7
|
+
|
8
|
+
def read
|
9
|
+
# Mock weather alerts data
|
10
|
+
alerts = [
|
11
|
+
{
|
12
|
+
type: "Severe Thunderstorm Warning",
|
13
|
+
location: "New York",
|
14
|
+
severity: "moderate",
|
15
|
+
expires: (Time.now + 3600).strftime("%Y-%m-%d %H:%M"),
|
16
|
+
description: "Severe thunderstorms capable of producing damaging winds and large hail"
|
17
|
+
},
|
18
|
+
{
|
19
|
+
type: "Heat Advisory",
|
20
|
+
location: "Phoenix",
|
21
|
+
severity: "minor",
|
22
|
+
expires: (Time.now + 7200).strftime("%Y-%m-%d %H:%M"),
|
23
|
+
description: "Temperatures expected to reach 110°F. Stay hydrated and avoid prolonged outdoor exposure"
|
24
|
+
},
|
25
|
+
{
|
26
|
+
type: "Winter Storm Watch",
|
27
|
+
location: "Chicago",
|
28
|
+
severity: "major",
|
29
|
+
expires: (Time.now + 10800).strftime("%Y-%m-%d %H:%M"),
|
30
|
+
description: "Heavy snow accumulation of 8-12 inches expected. Travel will be dangerous"
|
31
|
+
}
|
32
|
+
]
|
33
|
+
|
34
|
+
if alerts.empty?
|
35
|
+
"No active weather alerts at this time."
|
36
|
+
else
|
37
|
+
output = ["Active Weather Alerts:", ""]
|
38
|
+
|
39
|
+
alerts.each_with_index do |alert, i|
|
40
|
+
output << "#{i + 1}. #{alert[:type]} - #{alert[:location]}"
|
41
|
+
output << " Severity: #{alert[:severity].capitalize}"
|
42
|
+
output << " Expires: #{alert[:expires]}"
|
43
|
+
output << " #{alert[:description]}"
|
44
|
+
output << ""
|
45
|
+
end
|
46
|
+
|
47
|
+
output.join("\n").strip
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def set_server(server)
|
52
|
+
@server = server
|
53
|
+
|
54
|
+
define_singleton_method(:log) do |level, message, data: nil, logger: nil|
|
55
|
+
@server.log(level, message, data: data, logger: logger)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Weather
|
4
|
+
class GetCurrent < Tsikol::Tool
|
5
|
+
description "Get current weather for a location"
|
6
|
+
|
7
|
+
parameter :location do
|
8
|
+
type :string
|
9
|
+
required
|
10
|
+
description "City name or coordinates"
|
11
|
+
|
12
|
+
complete do |partial|
|
13
|
+
cities = [
|
14
|
+
"New York", "London", "Tokyo", "Paris", "Berlin",
|
15
|
+
"Sydney", "Toronto", "Mumbai", "Beijing", "Moscow",
|
16
|
+
"Los Angeles", "Chicago", "Houston", "Phoenix", "Philadelphia"
|
17
|
+
]
|
18
|
+
cities.select { |city| city.downcase.start_with?(partial.downcase) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
parameter :units do
|
23
|
+
type :string
|
24
|
+
optional
|
25
|
+
default "fahrenheit"
|
26
|
+
enum ["fahrenheit", "celsius", "kelvin"]
|
27
|
+
description "Temperature units"
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
@server = nil # Will be set by framework
|
32
|
+
end
|
33
|
+
|
34
|
+
def execute(location:, units: "fahrenheit")
|
35
|
+
# Log using server's log method if available
|
36
|
+
log :info, "Getting weather for #{location}" if respond_to?(:log)
|
37
|
+
|
38
|
+
# Mock temperature data
|
39
|
+
temps = {
|
40
|
+
"New York" => 72,
|
41
|
+
"London" => 61,
|
42
|
+
"Tokyo" => 77,
|
43
|
+
"Paris" => 64,
|
44
|
+
"Berlin" => 59
|
45
|
+
}
|
46
|
+
|
47
|
+
temp = temps[location] || 70
|
48
|
+
|
49
|
+
if temps[location]
|
50
|
+
log :debug, "Found temperature in cache", data: { location: location, temp: temp } if respond_to?(:log)
|
51
|
+
else
|
52
|
+
log :warning, "Location not found, using default", data: { location: location } if respond_to?(:log)
|
53
|
+
end
|
54
|
+
|
55
|
+
converted_temp = convert_temperature(temp, units)
|
56
|
+
"Currently #{converted_temp}° #{units} in #{location}"
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def convert_temperature(fahrenheit, to_units)
|
62
|
+
case to_units
|
63
|
+
when "celsius"
|
64
|
+
((fahrenheit - 32) * 5.0 / 9.0).round(1)
|
65
|
+
when "kelvin"
|
66
|
+
((fahrenheit - 32) * 5.0 / 9.0 + 273.15).round(1)
|
67
|
+
else
|
68
|
+
fahrenheit
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Allow server to inject log method
|
73
|
+
def set_server(server)
|
74
|
+
@server = server
|
75
|
+
|
76
|
+
# Define log method that delegates to server
|
77
|
+
define_singleton_method(:log) do |level, message, data: nil, logger: nil|
|
78
|
+
@server.log(level, message, data: data, logger: logger)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
module Weather
|
6
|
+
class GetForecast < Tsikol::Tool
|
7
|
+
description "Get weather forecast for a location"
|
8
|
+
|
9
|
+
parameter :location do
|
10
|
+
type :string
|
11
|
+
required
|
12
|
+
description "City name or coordinates"
|
13
|
+
|
14
|
+
complete do |partial|
|
15
|
+
cities = [
|
16
|
+
"New York", "London", "Tokyo", "Paris", "Berlin",
|
17
|
+
"Sydney", "Toronto", "Mumbai", "Beijing", "Moscow"
|
18
|
+
]
|
19
|
+
cities.select { |city| city.downcase.start_with?(partial.downcase) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
parameter :days do
|
24
|
+
type :integer
|
25
|
+
optional
|
26
|
+
default 5
|
27
|
+
description "Number of days to forecast (1-10)"
|
28
|
+
end
|
29
|
+
|
30
|
+
parameter :units do
|
31
|
+
type :string
|
32
|
+
optional
|
33
|
+
default "fahrenheit"
|
34
|
+
enum ["fahrenheit", "celsius", "kelvin"]
|
35
|
+
description "Temperature units"
|
36
|
+
end
|
37
|
+
|
38
|
+
def execute(location:, days: 5, units: "fahrenheit")
|
39
|
+
log :info, "Getting #{days}-day forecast for #{location}" if respond_to?(:log)
|
40
|
+
|
41
|
+
# Validate days
|
42
|
+
days = [[days, 1].max, 10].min
|
43
|
+
|
44
|
+
# Mock forecast data
|
45
|
+
base_temps = {
|
46
|
+
"New York" => 72,
|
47
|
+
"London" => 61,
|
48
|
+
"Tokyo" => 77,
|
49
|
+
"Paris" => 64,
|
50
|
+
"Berlin" => 59
|
51
|
+
}
|
52
|
+
|
53
|
+
base_temp = base_temps[location] || 70
|
54
|
+
forecast = []
|
55
|
+
|
56
|
+
days.times do |i|
|
57
|
+
# Add some variation
|
58
|
+
temp_variation = rand(-10..10)
|
59
|
+
temp = base_temp + temp_variation
|
60
|
+
converted_temp = convert_temperature(temp, units)
|
61
|
+
|
62
|
+
date = (Date.today + i + 1).strftime("%A, %B %d")
|
63
|
+
forecast << "#{date}: #{converted_temp}° #{units}"
|
64
|
+
end
|
65
|
+
|
66
|
+
"#{days}-day forecast for #{location}:\n#{forecast.join("\n")}"
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def convert_temperature(fahrenheit, to_units)
|
72
|
+
case to_units
|
73
|
+
when "celsius"
|
74
|
+
((fahrenheit - 32) * 5.0 / 9.0).round(1)
|
75
|
+
when "kelvin"
|
76
|
+
((fahrenheit - 32) * 5.0 / 9.0 + 273.15).round(1)
|
77
|
+
else
|
78
|
+
fahrenheit
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def set_server(server)
|
83
|
+
@server = server
|
84
|
+
|
85
|
+
define_singleton_method(:log) do |level, message, data: nil, logger: nil|
|
86
|
+
@server.log(level, message, data: data, logger: logger)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative '../../lib/tsikol'
|
5
|
+
|
6
|
+
# Require all components
|
7
|
+
require_relative 'app/tools/weather/get_current'
|
8
|
+
require_relative 'app/tools/weather/get_forecast'
|
9
|
+
require_relative 'app/resources/weather/alerts'
|
10
|
+
require_relative 'app/prompts/weather/chat'
|
11
|
+
|
12
|
+
# Start the weather service with inline routes
|
13
|
+
Tsikol.start(name: "weather-service") do
|
14
|
+
# Register components
|
15
|
+
tool Weather::GetCurrent
|
16
|
+
tool Weather::GetForecast
|
17
|
+
resource Weather::Alerts
|
18
|
+
prompt Weather::Chat
|
19
|
+
|
20
|
+
# Enable capabilities
|
21
|
+
logging true
|
22
|
+
completion true
|
23
|
+
|
24
|
+
# Simple inline resource
|
25
|
+
resource "server/health" do
|
26
|
+
"Weather service is healthy"
|
27
|
+
end
|
28
|
+
end
|
data/exe/tsikol
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# <%= @project_name.split(/[-_]/).map(&:capitalize).join(' ') %>
|
2
|
+
|
3
|
+
A Model Context Protocol (MCP) server built with Tsikol.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
```bash
|
8
|
+
bundle install
|
9
|
+
```
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
Start the server:
|
14
|
+
|
15
|
+
```bash
|
16
|
+
./server.rb
|
17
|
+
```
|
18
|
+
|
19
|
+
## Development
|
20
|
+
|
21
|
+
Generate new components:
|
22
|
+
|
23
|
+
```bash
|
24
|
+
# Generate a new tool
|
25
|
+
tsikol generate tool my_tool param1:string param2:integer
|
26
|
+
|
27
|
+
# Generate a new resource
|
28
|
+
tsikol generate resource my_resource
|
29
|
+
|
30
|
+
# Generate a new prompt
|
31
|
+
tsikol generate prompt my_prompt topic:string
|
32
|
+
```
|
33
|
+
|
34
|
+
## Testing
|
35
|
+
|
36
|
+
```bash
|
37
|
+
bundle exec rake test
|
38
|
+
```
|
@@ -0,0 +1,49 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/spec/examples.txt
|
9
|
+
/test/tmp/
|
10
|
+
/test/version_tmp/
|
11
|
+
/tmp/
|
12
|
+
|
13
|
+
# Used by dotenv library to load environment variables.
|
14
|
+
# .env
|
15
|
+
|
16
|
+
# Ignore Byebug command history file.
|
17
|
+
.byebug_history
|
18
|
+
|
19
|
+
## Specific to RubyMotion:
|
20
|
+
.dat*
|
21
|
+
.repl_history
|
22
|
+
build/
|
23
|
+
*.bridgesupport
|
24
|
+
build-iPhoneOS/
|
25
|
+
build-iPhoneSimulator/
|
26
|
+
|
27
|
+
## Documentation cache and generated files:
|
28
|
+
/.yardoc/
|
29
|
+
/_yardoc/
|
30
|
+
/doc/
|
31
|
+
/rdoc/
|
32
|
+
|
33
|
+
## Environment normalization:
|
34
|
+
/.bundle/
|
35
|
+
/vendor/bundle
|
36
|
+
/lib/bundler/man/
|
37
|
+
|
38
|
+
# MCP specific
|
39
|
+
/audit.log
|
40
|
+
/server_audit.log
|
41
|
+
/tsikol-debug.log
|
42
|
+
|
43
|
+
# IDE files
|
44
|
+
.idea/
|
45
|
+
.vscode/
|
46
|
+
*.swp
|
47
|
+
*.swo
|
48
|
+
*~
|
49
|
+
.DS_Store
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class <%= @class_name %> < Tsikol::Prompt
|
4
|
+
name "<%= @prompt_name %>"
|
5
|
+
description "TODO: Add description for <%= @prompt_name %>"
|
6
|
+
|
7
|
+
<% @arguments.each do |arg| -%>
|
8
|
+
argument :<%= arg[:name] %> do
|
9
|
+
type :<%= arg[:type] %>
|
10
|
+
<% if arg[:required] -%>
|
11
|
+
required
|
12
|
+
<% else -%>
|
13
|
+
optional
|
14
|
+
<% end -%>
|
15
|
+
description "TODO: Add description for <%= arg[:name] %>"
|
16
|
+
<% if arg[:type] == 'string' -%>
|
17
|
+
|
18
|
+
# Add completion for better UX
|
19
|
+
# complete do |partial|
|
20
|
+
# # Return array of possible values
|
21
|
+
# suggestions = ["suggestion1", "suggestion2", "suggestion3"]
|
22
|
+
# suggestions.select { |s| s.downcase.include?(partial.downcase) }
|
23
|
+
# end
|
24
|
+
<% end -%>
|
25
|
+
end
|
26
|
+
|
27
|
+
<% end -%>
|
28
|
+
def get_messages(<%= @arguments.map { |a| "#{a[:name]}:#{a[:required] ? '' : ' nil'}" }.join(', ') %>)
|
29
|
+
# TODO: Implement <%= @prompt_name %> logic
|
30
|
+
log :info, "Getting messages for <%= @prompt_name %>" if respond_to?(:log)
|
31
|
+
|
32
|
+
[
|
33
|
+
{
|
34
|
+
role: "user",
|
35
|
+
content: {
|
36
|
+
type: "text",
|
37
|
+
text: "TODO: Generate prompt based on arguments"
|
38
|
+
}
|
39
|
+
}
|
40
|
+
]
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def set_server(server)
|
46
|
+
@server = server
|
47
|
+
|
48
|
+
# Enable logging
|
49
|
+
define_singleton_method(:log) do |level, message, data: nil, logger: nil|
|
50
|
+
@server.log(level, message, data: data, logger: logger)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class <%= @class_name %> < Tsikol::Resource
|
4
|
+
uri "<%= @uri %>"
|
5
|
+
description "TODO: Add description for <%= @resource_name %>"
|
6
|
+
|
7
|
+
def read
|
8
|
+
# TODO: Implement <%= @resource_name %> logic
|
9
|
+
log :info, "Reading <%= @resource_name %>" if respond_to?(:log)
|
10
|
+
|
11
|
+
# Return the resource content
|
12
|
+
{
|
13
|
+
# Add your data here
|
14
|
+
example: "data",
|
15
|
+
timestamp: Time.now.iso8601
|
16
|
+
}.to_json
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def set_server(server)
|
22
|
+
@server = server
|
23
|
+
|
24
|
+
# Enable logging
|
25
|
+
define_singleton_method(:log) do |level, message, data: nil, logger: nil|
|
26
|
+
@server.log(level, message, data: data, logger: logger)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "tsikol"
|
6
|
+
|
7
|
+
# Start <%= @project_name %> MCP server
|
8
|
+
Tsikol.start(name: "<%= @project_name %>") do
|
9
|
+
# Components will be added here by the CLI
|
10
|
+
|
11
|
+
# Example inline resource
|
12
|
+
resource "health" do
|
13
|
+
"Server is healthy"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Enable capabilities as needed
|
17
|
+
# logging true
|
18
|
+
# completion true
|
19
|
+
# sampling true
|
20
|
+
|
21
|
+
# Add middleware as needed
|
22
|
+
# use Tsikol::LoggingMiddleware
|
23
|
+
# use Tsikol::RateLimitMiddleware, max_requests: 100, window: 60
|
24
|
+
end
|