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,108 @@
|
|
1
|
+
module Tsikol
|
2
|
+
class StdioTransport
|
3
|
+
def initialize(server)
|
4
|
+
@server = server
|
5
|
+
@server.set_transport(self)
|
6
|
+
end
|
7
|
+
|
8
|
+
def send_notification(notification)
|
9
|
+
notification_json = notification.to_json
|
10
|
+
$stdout.puts(notification_json)
|
11
|
+
$stdout.flush
|
12
|
+
end
|
13
|
+
|
14
|
+
def start
|
15
|
+
# Don't log to stderr during startup - MCP Inspector treats any stderr output as an error
|
16
|
+
|
17
|
+
# Run lifecycle hooks
|
18
|
+
@server.run_before_start_hooks if @server.respond_to?(:run_before_start_hooks)
|
19
|
+
@server.run_after_start_hooks if @server.respond_to?(:run_after_start_hooks)
|
20
|
+
|
21
|
+
# Set up signal handlers
|
22
|
+
setup_signal_handlers
|
23
|
+
|
24
|
+
loop do
|
25
|
+
# Read a line from stdin
|
26
|
+
line = $stdin.gets
|
27
|
+
break unless line
|
28
|
+
|
29
|
+
line = line.strip
|
30
|
+
next if line.empty?
|
31
|
+
|
32
|
+
# Check if this is a Content-Length header or raw JSON
|
33
|
+
if line.start_with?('Content-Length:')
|
34
|
+
# Handle Content-Length format
|
35
|
+
headers = { 'content-length' => line.split(':', 2)[1].strip }
|
36
|
+
|
37
|
+
# Read remaining headers
|
38
|
+
while header_line = $stdin.gets
|
39
|
+
header_line = header_line.strip
|
40
|
+
break if header_line.empty?
|
41
|
+
|
42
|
+
key, value = header_line.split(': ', 2)
|
43
|
+
headers[key.downcase] = value if key && value
|
44
|
+
end
|
45
|
+
|
46
|
+
# Read body based on content length
|
47
|
+
content_length = headers['content-length'].to_i
|
48
|
+
body = $stdin.read(content_length)
|
49
|
+
else
|
50
|
+
# Handle raw JSON format (what MCP Inspector sends)
|
51
|
+
body = line
|
52
|
+
end
|
53
|
+
|
54
|
+
begin
|
55
|
+
# Log incoming message for debugging
|
56
|
+
File.open('/tmp/tsikol-debug.log', 'a') do |f|
|
57
|
+
f.puts "#{Time.now}: Received message: #{body}"
|
58
|
+
end
|
59
|
+
|
60
|
+
# Process message and get response
|
61
|
+
response = @server.handle_message(body)
|
62
|
+
|
63
|
+
# Only send response if not nil
|
64
|
+
if response
|
65
|
+
# Write response as plain JSON line (what MCP Inspector expects)
|
66
|
+
response_json = response.to_json
|
67
|
+
$stdout.puts(response_json)
|
68
|
+
$stdout.flush
|
69
|
+
|
70
|
+
# Log response for debugging
|
71
|
+
File.open('/tmp/tsikol-debug.log', 'a') do |f|
|
72
|
+
f.puts "#{Time.now}: Sent response: #{response_json}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
rescue => e
|
76
|
+
# Log errors to stderr
|
77
|
+
$stderr.puts "Error: #{e.message}"
|
78
|
+
$stderr.puts e.backtrace.join("\n")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
rescue EOFError
|
82
|
+
$stderr.puts "Connection closed"
|
83
|
+
rescue Interrupt
|
84
|
+
$stderr.puts "Server stopped"
|
85
|
+
ensure
|
86
|
+
shutdown
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def setup_signal_handlers
|
92
|
+
%w[INT TERM].each do |signal|
|
93
|
+
Signal.trap(signal) do
|
94
|
+
$stderr.puts "Received #{signal} signal, shutting down..."
|
95
|
+
shutdown
|
96
|
+
exit(0)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def shutdown
|
102
|
+
return unless @server.respond_to?(:run_before_stop_hooks)
|
103
|
+
|
104
|
+
@server.run_before_stop_hooks
|
105
|
+
@server.run_after_stop_hooks
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,261 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Tsikol
|
6
|
+
module TestHelpers
|
7
|
+
# Mock MCP client for testing servers
|
8
|
+
class TestClient
|
9
|
+
attr_reader :server, :responses, :notifications
|
10
|
+
|
11
|
+
def initialize(server)
|
12
|
+
@server = server
|
13
|
+
@responses = []
|
14
|
+
@notifications = []
|
15
|
+
@request_id = 0
|
16
|
+
|
17
|
+
# Capture notifications
|
18
|
+
if server.respond_to?(:set_transport)
|
19
|
+
server.set_transport(self)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Send notification (called by server)
|
24
|
+
def send_notification(notification)
|
25
|
+
@notifications << notification
|
26
|
+
end
|
27
|
+
|
28
|
+
# Initialize connection
|
29
|
+
def initialize_connection(client_info = {})
|
30
|
+
request("initialize", {
|
31
|
+
protocolVersion: Tsikol::PROTOCOL_VERSION,
|
32
|
+
capabilities: client_info[:capabilities] || {},
|
33
|
+
clientInfo: {
|
34
|
+
name: client_info[:name] || "test-client",
|
35
|
+
version: client_info[:version] || "1.0.0"
|
36
|
+
}
|
37
|
+
})
|
38
|
+
end
|
39
|
+
|
40
|
+
# Make a request to the server
|
41
|
+
def request(method, params = nil)
|
42
|
+
message = {
|
43
|
+
"jsonrpc" => "2.0",
|
44
|
+
"id" => next_id,
|
45
|
+
"method" => method
|
46
|
+
}
|
47
|
+
message["params"] = params if params
|
48
|
+
|
49
|
+
response = @server.handle_message(message.to_json)
|
50
|
+
@responses << response
|
51
|
+
response
|
52
|
+
end
|
53
|
+
|
54
|
+
# Call a tool
|
55
|
+
def call_tool(name, arguments = {})
|
56
|
+
request("tools/call", {
|
57
|
+
"name" => name,
|
58
|
+
"arguments" => arguments
|
59
|
+
})
|
60
|
+
end
|
61
|
+
|
62
|
+
# Read a resource
|
63
|
+
def read_resource(uri)
|
64
|
+
request("resources/read", { "uri" => uri })
|
65
|
+
end
|
66
|
+
|
67
|
+
# Get a prompt
|
68
|
+
def get_prompt(name, arguments = {})
|
69
|
+
request("prompts/get", {
|
70
|
+
"name" => name,
|
71
|
+
"arguments" => arguments
|
72
|
+
})
|
73
|
+
end
|
74
|
+
|
75
|
+
# List available tools
|
76
|
+
def list_tools
|
77
|
+
request("tools/list")
|
78
|
+
end
|
79
|
+
|
80
|
+
# List available resources
|
81
|
+
def list_resources
|
82
|
+
request("resources/list")
|
83
|
+
end
|
84
|
+
|
85
|
+
# List available prompts
|
86
|
+
def list_prompts
|
87
|
+
request("prompts/list")
|
88
|
+
end
|
89
|
+
|
90
|
+
# Get completion suggestions
|
91
|
+
def complete(ref, argument = nil)
|
92
|
+
params = { "ref" => ref }
|
93
|
+
params["argument"] = argument if argument
|
94
|
+
request("completion/complete", params)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Request sampling
|
98
|
+
def sample(messages, options = {})
|
99
|
+
params = {
|
100
|
+
"messages" => messages,
|
101
|
+
"modelPreferences" => options[:model_preferences] || {},
|
102
|
+
"systemPrompt" => options[:system_prompt],
|
103
|
+
"maxTokens" => options[:max_tokens]
|
104
|
+
}
|
105
|
+
request("sampling/createMessage", params)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Set logging level
|
109
|
+
def set_log_level(level)
|
110
|
+
request("logging/setLevel", { "level" => level })
|
111
|
+
end
|
112
|
+
|
113
|
+
# Send ping
|
114
|
+
def ping
|
115
|
+
request("ping")
|
116
|
+
end
|
117
|
+
|
118
|
+
# Get last response
|
119
|
+
def last_response
|
120
|
+
@responses.last
|
121
|
+
end
|
122
|
+
|
123
|
+
# Get last notification
|
124
|
+
def last_notification
|
125
|
+
@notifications.last
|
126
|
+
end
|
127
|
+
|
128
|
+
# Clear captured data
|
129
|
+
def clear!
|
130
|
+
@responses.clear
|
131
|
+
@notifications.clear
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def next_id
|
137
|
+
@request_id += 1
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# RSpec matchers
|
142
|
+
module Matchers
|
143
|
+
# Check if response was successful
|
144
|
+
class BeSuccessful
|
145
|
+
def matches?(response)
|
146
|
+
response && !response[:error]
|
147
|
+
end
|
148
|
+
|
149
|
+
def failure_message
|
150
|
+
"expected response to be successful, but got error: #{@response[:error]}"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Check if response has error
|
155
|
+
class HaveError
|
156
|
+
def initialize(code = nil, message = nil)
|
157
|
+
@expected_code = code
|
158
|
+
@expected_message = message
|
159
|
+
end
|
160
|
+
|
161
|
+
def matches?(response)
|
162
|
+
return false unless response && response[:error]
|
163
|
+
|
164
|
+
if @expected_code
|
165
|
+
return false unless response[:error][:code] == @expected_code
|
166
|
+
end
|
167
|
+
|
168
|
+
if @expected_message
|
169
|
+
return false unless response[:error][:message].include?(@expected_message)
|
170
|
+
end
|
171
|
+
|
172
|
+
true
|
173
|
+
end
|
174
|
+
|
175
|
+
def failure_message
|
176
|
+
"expected response to have error #{@expected_code} with message containing '#{@expected_message}'"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Check if server has capability
|
181
|
+
class HaveCapability
|
182
|
+
def initialize(capability)
|
183
|
+
@capability = capability
|
184
|
+
end
|
185
|
+
|
186
|
+
def matches?(server_or_response)
|
187
|
+
if server_or_response.is_a?(Hash)
|
188
|
+
# Response from initialize
|
189
|
+
capabilities = server_or_response.dig(:result, :capabilities) || {}
|
190
|
+
else
|
191
|
+
# Server instance
|
192
|
+
capabilities = server_or_response.instance_variable_get(:@server_capabilities) || {}
|
193
|
+
end
|
194
|
+
|
195
|
+
capabilities.key?(@capability.to_sym) || capabilities.key?(@capability.to_s)
|
196
|
+
end
|
197
|
+
|
198
|
+
def failure_message
|
199
|
+
"expected server to have capability :#{@capability}"
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def be_successful
|
204
|
+
BeSuccessful.new
|
205
|
+
end
|
206
|
+
|
207
|
+
def have_error(code = nil, message = nil)
|
208
|
+
HaveError.new(code, message)
|
209
|
+
end
|
210
|
+
|
211
|
+
def have_capability(capability)
|
212
|
+
HaveCapability.new(capability)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Test assertions
|
217
|
+
module Assertions
|
218
|
+
def assert_successful_response(response)
|
219
|
+
assert response && !response[:error],
|
220
|
+
"Expected successful response, got: #{response.inspect}"
|
221
|
+
end
|
222
|
+
|
223
|
+
def assert_error_response(response, code = nil, message_pattern = nil)
|
224
|
+
assert response && response[:error],
|
225
|
+
"Expected error response, got: #{response.inspect}"
|
226
|
+
|
227
|
+
if code
|
228
|
+
assert_equal code, response[:error][:code],
|
229
|
+
"Expected error code #{code}, got: #{response[:error][:code]}"
|
230
|
+
end
|
231
|
+
|
232
|
+
if message_pattern
|
233
|
+
assert_match message_pattern, response[:error][:message],
|
234
|
+
"Expected error message to match #{message_pattern}"
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def assert_has_capability(server, capability)
|
239
|
+
capabilities = server.instance_variable_get(:@server_capabilities) || {}
|
240
|
+
assert capabilities.key?(capability.to_sym) || capabilities.key?(capability.to_s),
|
241
|
+
"Expected server to have capability :#{capability}"
|
242
|
+
end
|
243
|
+
|
244
|
+
def assert_tool_result(client, tool_name, arguments, expected)
|
245
|
+
response = client.call_tool(tool_name, arguments)
|
246
|
+
assert_successful_response(response)
|
247
|
+
|
248
|
+
result = response.dig(:result, :content, 0, :text)
|
249
|
+
assert_equal expected, result
|
250
|
+
end
|
251
|
+
|
252
|
+
def assert_resource_content(client, uri, expected)
|
253
|
+
response = client.read_resource(uri)
|
254
|
+
assert_successful_response(response)
|
255
|
+
|
256
|
+
content = response.dig(:result, :contents, 0, :text)
|
257
|
+
assert_equal expected, content
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
data/lib/tsikol/tool.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tsikol
|
4
|
+
class Tool
|
5
|
+
class << self
|
6
|
+
attr_reader :tool_description, :parameters_config
|
7
|
+
|
8
|
+
def description(desc)
|
9
|
+
@tool_description = desc
|
10
|
+
end
|
11
|
+
|
12
|
+
def parameter(name, &block)
|
13
|
+
@parameters_config ||= {}
|
14
|
+
param = ParameterBuilder.new(name)
|
15
|
+
param.instance_eval(&block) if block_given?
|
16
|
+
@parameters_config[name] = param.build
|
17
|
+
end
|
18
|
+
|
19
|
+
def tool_name
|
20
|
+
# Convert class name to tool name
|
21
|
+
# Weather::GetCurrent -> weather:get_current
|
22
|
+
name.gsub('::', ':').gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def description
|
27
|
+
self.class.tool_description || "Tool: #{self.class.tool_name}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def parameters
|
31
|
+
self.class.parameters_config || {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def execute(**args)
|
35
|
+
raise NotImplementedError, "Subclasses must implement execute method"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Convert to MCP format
|
39
|
+
def to_mcp
|
40
|
+
{
|
41
|
+
name: self.class.tool_name,
|
42
|
+
description: description,
|
43
|
+
inputSchema: build_input_schema
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def build_input_schema
|
50
|
+
properties = {}
|
51
|
+
required = []
|
52
|
+
|
53
|
+
parameters.each do |name, config|
|
54
|
+
properties[name.to_s] = {
|
55
|
+
type: config[:type].to_s,
|
56
|
+
description: config[:description]
|
57
|
+
}
|
58
|
+
|
59
|
+
properties[name.to_s][:enum] = config[:enum] if config[:enum]
|
60
|
+
properties[name.to_s][:default] = config[:default] if config.key?(:default)
|
61
|
+
|
62
|
+
required << name.to_s if config[:required]
|
63
|
+
end
|
64
|
+
|
65
|
+
{
|
66
|
+
type: "object",
|
67
|
+
properties: properties,
|
68
|
+
required: required
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class ParameterBuilder
|
74
|
+
def initialize(name)
|
75
|
+
@name = name
|
76
|
+
@config = { name: name }
|
77
|
+
end
|
78
|
+
|
79
|
+
def type(t)
|
80
|
+
@config[:type] = t
|
81
|
+
end
|
82
|
+
|
83
|
+
def required(val = true)
|
84
|
+
@config[:required] = val
|
85
|
+
end
|
86
|
+
|
87
|
+
def optional
|
88
|
+
@config[:required] = false
|
89
|
+
end
|
90
|
+
|
91
|
+
def description(desc)
|
92
|
+
@config[:description] = desc
|
93
|
+
end
|
94
|
+
|
95
|
+
def default(val)
|
96
|
+
@config[:default] = val
|
97
|
+
end
|
98
|
+
|
99
|
+
def enum(values)
|
100
|
+
@config[:enum] = values
|
101
|
+
end
|
102
|
+
|
103
|
+
def complete(&block)
|
104
|
+
@config[:completion] = block
|
105
|
+
end
|
106
|
+
|
107
|
+
def build
|
108
|
+
@config
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/tsikol.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative 'tsikol/version'
|
3
|
+
require_relative 'tsikol/server'
|
4
|
+
require_relative 'tsikol/stdio_transport'
|
5
|
+
require_relative 'tsikol/http_transport'
|
6
|
+
require_relative 'tsikol/tool'
|
7
|
+
require_relative 'tsikol/resource'
|
8
|
+
require_relative 'tsikol/prompt'
|
9
|
+
require_relative 'tsikol/router'
|
10
|
+
require_relative 'tsikol/middleware'
|
11
|
+
require_relative 'tsikol/error_handler'
|
12
|
+
require_relative 'tsikol/lifecycle'
|
13
|
+
require_relative 'tsikol/health'
|
14
|
+
require_relative 'tsikol/cli' if defined?(Thor)
|
15
|
+
|
16
|
+
module Tsikol
|
17
|
+
|
18
|
+
PROTOCOL_VERSION = "2025-06-18"
|
19
|
+
|
20
|
+
module Errors
|
21
|
+
PARSE_ERROR = -32700
|
22
|
+
INVALID_REQUEST = -32600
|
23
|
+
METHOD_NOT_FOUND = -32601
|
24
|
+
INVALID_PARAMS = -32602
|
25
|
+
INTERNAL_ERROR = -32603
|
26
|
+
end
|
27
|
+
|
28
|
+
# For stdio transport (MCP Inspector, CLI tools)
|
29
|
+
def self.server(name, version: VERSION, &block)
|
30
|
+
server = Server.new(name: name, version: version)
|
31
|
+
server.instance_eval(&block) if block_given?
|
32
|
+
|
33
|
+
transport = StdioTransport.new(server)
|
34
|
+
transport.start
|
35
|
+
end
|
36
|
+
|
37
|
+
# For HTTP transport (web services)
|
38
|
+
def self.http_server(name, version: VERSION, port: 4567, &block)
|
39
|
+
server = Server.new(name: name, version: version)
|
40
|
+
server.instance_eval(&block) if block_given?
|
41
|
+
|
42
|
+
app = HttpTransport.new(server)
|
43
|
+
app.run! do |server|
|
44
|
+
server.port = port
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# New structure with routes file or inline routes
|
49
|
+
def self.start(name: nil, version: VERSION, transport: :stdio, &block)
|
50
|
+
# Determine server name
|
51
|
+
server_name = name || "tsikol-server"
|
52
|
+
|
53
|
+
# Create server
|
54
|
+
server = Server.new(name: server_name, version: version)
|
55
|
+
|
56
|
+
# Check if block contains route definitions
|
57
|
+
if block_given?
|
58
|
+
# Create a router and evaluate the block
|
59
|
+
router = Router.new(server)
|
60
|
+
router.instance_eval(&block)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Start transport
|
64
|
+
case transport
|
65
|
+
when :stdio
|
66
|
+
transport = StdioTransport.new(server)
|
67
|
+
transport.start
|
68
|
+
when :http
|
69
|
+
# TODO: HTTP transport
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|