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,651 @@
|
|
1
|
+
# Resource API Reference
|
2
|
+
|
3
|
+
The `Tsikol::Resource` class provides the foundation for creating resources that expose read-only data to AI assistants.
|
4
|
+
|
5
|
+
## Class: Tsikol::Resource
|
6
|
+
|
7
|
+
### Class Methods
|
8
|
+
|
9
|
+
#### `.uri(path = nil)`
|
10
|
+
|
11
|
+
Set or get the resource URI.
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
class SystemStatus < Tsikol::Resource
|
15
|
+
uri "system/status"
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
#### `.description(text = nil)`
|
20
|
+
|
21
|
+
Set or get the resource description.
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
class ConfigResource < Tsikol::Resource
|
25
|
+
uri "config"
|
26
|
+
description "Application configuration values"
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
### Instance Methods
|
31
|
+
|
32
|
+
#### `#read`
|
33
|
+
|
34
|
+
Main method to implement resource logic. Must return a string (usually JSON).
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
class VersionResource < Tsikol::Resource
|
38
|
+
uri "version"
|
39
|
+
description "Current application version"
|
40
|
+
|
41
|
+
def read
|
42
|
+
{
|
43
|
+
version: "1.0.0",
|
44
|
+
build: "2024.01.15",
|
45
|
+
git_commit: ENV['GIT_COMMIT'] || "unknown"
|
46
|
+
}.to_json
|
47
|
+
end
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
#### `#uri`
|
52
|
+
|
53
|
+
Get the resource URI (instance method).
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
resource = SystemStatus.new
|
57
|
+
resource.uri # => "system/status"
|
58
|
+
```
|
59
|
+
|
60
|
+
#### `#set_server(server)`
|
61
|
+
|
62
|
+
Called when resource is registered with a server. Override to access server features.
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
def set_server(server)
|
66
|
+
@server = server
|
67
|
+
@start_time = Time.now
|
68
|
+
|
69
|
+
# Enable logging if available
|
70
|
+
if @server.logging_enabled?
|
71
|
+
define_singleton_method(:log) do |level, message, data: nil|
|
72
|
+
@server.log(level, message, data: data)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
### Resource Registration
|
79
|
+
|
80
|
+
Resources can be registered in several ways:
|
81
|
+
|
82
|
+
#### Class Registration
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
server.register_resource_class(SystemStatus)
|
86
|
+
# or
|
87
|
+
server.resource SystemStatus
|
88
|
+
```
|
89
|
+
|
90
|
+
#### Instance Registration
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
resource = SystemStatus.new
|
94
|
+
server.register_resource_instance(resource)
|
95
|
+
```
|
96
|
+
|
97
|
+
#### Inline Registration
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
server.register_resource("time") do
|
101
|
+
Time.now.iso8601
|
102
|
+
end
|
103
|
+
# or
|
104
|
+
server.resource "time" do
|
105
|
+
Time.now.iso8601
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
### URI Patterns
|
110
|
+
|
111
|
+
Resources use URI-style paths for organization:
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
# Top-level resource
|
115
|
+
class RootConfig < Tsikol::Resource
|
116
|
+
uri "config"
|
117
|
+
end
|
118
|
+
|
119
|
+
# Nested resource
|
120
|
+
class DatabaseConfig < Tsikol::Resource
|
121
|
+
uri "config/database"
|
122
|
+
end
|
123
|
+
|
124
|
+
# Deep nesting
|
125
|
+
class UserPreferences < Tsikol::Resource
|
126
|
+
uri "users/current/preferences"
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
### Dynamic Resources
|
131
|
+
|
132
|
+
Resources can return different data based on context:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
class MetricsResource < Tsikol::Resource
|
136
|
+
uri "metrics"
|
137
|
+
description "Real-time system metrics"
|
138
|
+
|
139
|
+
def read
|
140
|
+
{
|
141
|
+
timestamp: Time.now.iso8601,
|
142
|
+
cpu_usage: current_cpu_usage,
|
143
|
+
memory_usage: current_memory_usage,
|
144
|
+
active_connections: connection_count,
|
145
|
+
request_rate: calculate_request_rate
|
146
|
+
}.to_json
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def current_cpu_usage
|
152
|
+
# Implementation specific
|
153
|
+
"#{rand(0..100)}%"
|
154
|
+
end
|
155
|
+
|
156
|
+
def current_memory_usage
|
157
|
+
# Get actual memory usage
|
158
|
+
rss = `ps -o rss= -p #{Process.pid}`.to_i
|
159
|
+
"#{(rss / 1024.0).round(2)}MB"
|
160
|
+
end
|
161
|
+
|
162
|
+
def connection_count
|
163
|
+
Thread.list.select(&:alive?).count
|
164
|
+
end
|
165
|
+
|
166
|
+
def calculate_request_rate
|
167
|
+
# Calculate from server metrics
|
168
|
+
@server.respond_to?(:request_count) ? @server.request_count : 0
|
169
|
+
end
|
170
|
+
end
|
171
|
+
```
|
172
|
+
|
173
|
+
### Resource Formats
|
174
|
+
|
175
|
+
While resources typically return JSON, they can return any string format:
|
176
|
+
|
177
|
+
#### JSON (Most Common)
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
class JsonResource < Tsikol::Resource
|
181
|
+
uri "data/users"
|
182
|
+
|
183
|
+
def read
|
184
|
+
users = fetch_users
|
185
|
+
users.map { |u| { id: u.id, name: u.name } }.to_json
|
186
|
+
end
|
187
|
+
end
|
188
|
+
```
|
189
|
+
|
190
|
+
#### Plain Text
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
class TextResource < Tsikol::Resource
|
194
|
+
uri "docs/readme"
|
195
|
+
|
196
|
+
def read
|
197
|
+
File.read("README.md")
|
198
|
+
end
|
199
|
+
end
|
200
|
+
```
|
201
|
+
|
202
|
+
#### CSV
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
class CsvResource < Tsikol::Resource
|
206
|
+
uri "data/export.csv"
|
207
|
+
|
208
|
+
def read
|
209
|
+
require 'csv'
|
210
|
+
|
211
|
+
CSV.generate do |csv|
|
212
|
+
csv << ["ID", "Name", "Email"]
|
213
|
+
users.each do |user|
|
214
|
+
csv << [user.id, user.name, user.email]
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
```
|
220
|
+
|
221
|
+
#### XML
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
class XmlResource < Tsikol::Resource
|
225
|
+
uri "data/config.xml"
|
226
|
+
|
227
|
+
def read
|
228
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
229
|
+
xml.configuration {
|
230
|
+
xml.database {
|
231
|
+
xml.host ENV['DB_HOST']
|
232
|
+
xml.port ENV['DB_PORT']
|
233
|
+
}
|
234
|
+
}
|
235
|
+
end
|
236
|
+
builder.to_xml
|
237
|
+
end
|
238
|
+
end
|
239
|
+
```
|
240
|
+
|
241
|
+
### Caching
|
242
|
+
|
243
|
+
Implement caching for expensive operations:
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
class CachedResource < Tsikol::Resource
|
247
|
+
uri "expensive/data"
|
248
|
+
description "Expensive computation (cached)"
|
249
|
+
|
250
|
+
def initialize
|
251
|
+
super
|
252
|
+
@cache = nil
|
253
|
+
@cache_time = nil
|
254
|
+
@cache_ttl = 300 # 5 minutes
|
255
|
+
end
|
256
|
+
|
257
|
+
def read
|
258
|
+
if cache_valid?
|
259
|
+
log :debug, "Cache hit"
|
260
|
+
@cache
|
261
|
+
else
|
262
|
+
log :debug, "Cache miss, computing..."
|
263
|
+
@cache = compute_expensive_data
|
264
|
+
@cache_time = Time.now
|
265
|
+
@cache
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
private
|
270
|
+
|
271
|
+
def cache_valid?
|
272
|
+
return false unless @cache && @cache_time
|
273
|
+
Time.now - @cache_time < @cache_ttl
|
274
|
+
end
|
275
|
+
|
276
|
+
def compute_expensive_data
|
277
|
+
# Simulate expensive operation
|
278
|
+
sleep(0.5)
|
279
|
+
|
280
|
+
{
|
281
|
+
computed_at: Time.now.iso8601,
|
282
|
+
result: perform_calculation,
|
283
|
+
valid_for: @cache_ttl
|
284
|
+
}.to_json
|
285
|
+
end
|
286
|
+
end
|
287
|
+
```
|
288
|
+
|
289
|
+
### Error Handling
|
290
|
+
|
291
|
+
Resources should handle errors gracefully:
|
292
|
+
|
293
|
+
```ruby
|
294
|
+
class SafeResource < Tsikol::Resource
|
295
|
+
uri "external/api"
|
296
|
+
description "Data from external API"
|
297
|
+
|
298
|
+
def read
|
299
|
+
response = fetch_external_data
|
300
|
+
format_response(response)
|
301
|
+
|
302
|
+
rescue Net::HTTPError => e
|
303
|
+
log :error, "HTTP error fetching data", error: e.message
|
304
|
+
error_response("External API unavailable")
|
305
|
+
|
306
|
+
rescue JSON::ParserError => e
|
307
|
+
log :error, "Invalid JSON from API", error: e.message
|
308
|
+
error_response("Invalid data format")
|
309
|
+
|
310
|
+
rescue => e
|
311
|
+
log :error, "Unexpected error", error: e.class.name
|
312
|
+
error_response("An error occurred")
|
313
|
+
end
|
314
|
+
|
315
|
+
private
|
316
|
+
|
317
|
+
def error_response(message)
|
318
|
+
{
|
319
|
+
error: message,
|
320
|
+
timestamp: Time.now.iso8601
|
321
|
+
}.to_json
|
322
|
+
end
|
323
|
+
end
|
324
|
+
```
|
325
|
+
|
326
|
+
### Using Server Features
|
327
|
+
|
328
|
+
Resources can access server capabilities:
|
329
|
+
|
330
|
+
```ruby
|
331
|
+
class ServerAwareResource < Tsikol::Resource
|
332
|
+
uri "server/info"
|
333
|
+
description "Information about this server"
|
334
|
+
|
335
|
+
def read
|
336
|
+
info = {
|
337
|
+
name: @server.name,
|
338
|
+
version: @server.version,
|
339
|
+
uptime: calculate_uptime,
|
340
|
+
capabilities: list_capabilities
|
341
|
+
}
|
342
|
+
|
343
|
+
# Add request count if metrics available
|
344
|
+
if @server.respond_to?(:metrics)
|
345
|
+
info[:total_requests] = @server.metrics[:request_count]
|
346
|
+
end
|
347
|
+
|
348
|
+
info.to_json
|
349
|
+
end
|
350
|
+
|
351
|
+
private
|
352
|
+
|
353
|
+
def calculate_uptime
|
354
|
+
return 0 unless @start_time
|
355
|
+
Time.now - @start_time
|
356
|
+
end
|
357
|
+
|
358
|
+
def list_capabilities
|
359
|
+
caps = []
|
360
|
+
caps << "logging" if @server.logging_enabled?
|
361
|
+
caps << "completion" if @server.completion_enabled?
|
362
|
+
caps << "sampling" if @server.sampling_enabled?
|
363
|
+
caps
|
364
|
+
end
|
365
|
+
|
366
|
+
def set_server(server)
|
367
|
+
@server = server
|
368
|
+
@start_time = Time.now
|
369
|
+
end
|
370
|
+
end
|
371
|
+
```
|
372
|
+
|
373
|
+
## Complete Example
|
374
|
+
|
375
|
+
```ruby
|
376
|
+
class SystemMetrics < Tsikol::Resource
|
377
|
+
uri "system/metrics"
|
378
|
+
description "Comprehensive system metrics and statistics"
|
379
|
+
|
380
|
+
def initialize
|
381
|
+
super
|
382
|
+
@metrics_cache = {}
|
383
|
+
@last_cpu_check = Time.now
|
384
|
+
@last_cpu_value = 0
|
385
|
+
end
|
386
|
+
|
387
|
+
def read
|
388
|
+
{
|
389
|
+
server: server_metrics,
|
390
|
+
system: system_metrics,
|
391
|
+
process: process_metrics,
|
392
|
+
custom: custom_metrics,
|
393
|
+
timestamp: Time.now.iso8601
|
394
|
+
}.to_json
|
395
|
+
rescue => e
|
396
|
+
log :error, "Error collecting metrics", error: e.message
|
397
|
+
minimal_metrics.to_json
|
398
|
+
end
|
399
|
+
|
400
|
+
private
|
401
|
+
|
402
|
+
def server_metrics
|
403
|
+
{
|
404
|
+
name: @server.name,
|
405
|
+
version: @server.version,
|
406
|
+
uptime_seconds: uptime,
|
407
|
+
total_requests: request_count,
|
408
|
+
active_tools: @server.tools.keys.count,
|
409
|
+
active_resources: @server.resources.keys.count
|
410
|
+
}
|
411
|
+
end
|
412
|
+
|
413
|
+
def system_metrics
|
414
|
+
{
|
415
|
+
platform: RUBY_PLATFORM,
|
416
|
+
ruby_version: RUBY_VERSION,
|
417
|
+
cpu_count: cpu_count,
|
418
|
+
total_memory: total_memory,
|
419
|
+
load_average: load_average
|
420
|
+
}
|
421
|
+
end
|
422
|
+
|
423
|
+
def process_metrics
|
424
|
+
{
|
425
|
+
pid: Process.pid,
|
426
|
+
memory_usage_mb: process_memory,
|
427
|
+
cpu_usage_percent: process_cpu,
|
428
|
+
thread_count: Thread.list.count,
|
429
|
+
gc_stats: GC.stat
|
430
|
+
}
|
431
|
+
end
|
432
|
+
|
433
|
+
def custom_metrics
|
434
|
+
@server.respond_to?(:custom_metrics) ? @server.custom_metrics : {}
|
435
|
+
end
|
436
|
+
|
437
|
+
def uptime
|
438
|
+
@start_time ? (Time.now - @start_time).to_i : 0
|
439
|
+
end
|
440
|
+
|
441
|
+
def request_count
|
442
|
+
@server.respond_to?(:request_count) ? @server.request_count : 0
|
443
|
+
end
|
444
|
+
|
445
|
+
def cpu_count
|
446
|
+
@metrics_cache[:cpu_count] ||= begin
|
447
|
+
if RUBY_PLATFORM =~ /linux/
|
448
|
+
`nproc`.to_i
|
449
|
+
elsif RUBY_PLATFORM =~ /darwin/
|
450
|
+
`sysctl -n hw.ncpu`.to_i
|
451
|
+
else
|
452
|
+
1
|
453
|
+
end
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
def total_memory
|
458
|
+
@metrics_cache[:total_memory] ||= begin
|
459
|
+
if RUBY_PLATFORM =~ /linux/
|
460
|
+
`grep MemTotal /proc/meminfo`.match(/\d+/)[0].to_i / 1024
|
461
|
+
elsif RUBY_PLATFORM =~ /darwin/
|
462
|
+
`sysctl -n hw.memsize`.to_i / 1024 / 1024
|
463
|
+
else
|
464
|
+
0
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
def load_average
|
470
|
+
if File.exist?("/proc/loadavg")
|
471
|
+
File.read("/proc/loadavg").split.first(3).map(&:to_f)
|
472
|
+
else
|
473
|
+
[0.0, 0.0, 0.0]
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
def process_memory
|
478
|
+
rss = `ps -o rss= -p #{Process.pid}`.to_i
|
479
|
+
(rss / 1024.0).round(2)
|
480
|
+
end
|
481
|
+
|
482
|
+
def process_cpu
|
483
|
+
# Simple CPU calculation
|
484
|
+
now = Time.now
|
485
|
+
if now - @last_cpu_check > 1
|
486
|
+
# Simplified CPU usage
|
487
|
+
@last_cpu_value = rand(0..20)
|
488
|
+
@last_cpu_check = now
|
489
|
+
end
|
490
|
+
@last_cpu_value
|
491
|
+
end
|
492
|
+
|
493
|
+
def minimal_metrics
|
494
|
+
{
|
495
|
+
error: "Metrics collection failed",
|
496
|
+
server_name: @server.name,
|
497
|
+
timestamp: Time.now.iso8601
|
498
|
+
}
|
499
|
+
end
|
500
|
+
|
501
|
+
def set_server(server)
|
502
|
+
@server = server
|
503
|
+
@start_time = Time.now
|
504
|
+
|
505
|
+
if @server.logging_enabled?
|
506
|
+
define_singleton_method(:log) do |level, message, data: nil|
|
507
|
+
@server.log(level, message, data: data)
|
508
|
+
end
|
509
|
+
else
|
510
|
+
define_singleton_method(:log) do |*args|
|
511
|
+
# No-op
|
512
|
+
end
|
513
|
+
end
|
514
|
+
end
|
515
|
+
end
|
516
|
+
```
|
517
|
+
|
518
|
+
## Testing Resources
|
519
|
+
|
520
|
+
```ruby
|
521
|
+
require 'minitest/autorun'
|
522
|
+
require 'tsikol/test_helpers'
|
523
|
+
|
524
|
+
class SystemMetricsTest < Minitest::Test
|
525
|
+
include Tsikol::TestHelpers::Assertions
|
526
|
+
|
527
|
+
def setup
|
528
|
+
@server = Tsikol::Server.new(name: "test")
|
529
|
+
@server.logging true
|
530
|
+
@server.register_resource_instance(SystemMetrics.new)
|
531
|
+
@client = Tsikol::TestHelpers::TestClient.new(@server)
|
532
|
+
@client.initialize_connection
|
533
|
+
end
|
534
|
+
|
535
|
+
def test_read_metrics
|
536
|
+
response = @client.read_resource("system/metrics")
|
537
|
+
|
538
|
+
assert_successful_response(response)
|
539
|
+
|
540
|
+
content = response.dig(:result, :contents, 0, :text)
|
541
|
+
data = JSON.parse(content)
|
542
|
+
|
543
|
+
assert data["server"]
|
544
|
+
assert data["system"]
|
545
|
+
assert data["process"]
|
546
|
+
assert data["timestamp"]
|
547
|
+
end
|
548
|
+
|
549
|
+
def test_metrics_structure
|
550
|
+
response = @client.read_resource("system/metrics")
|
551
|
+
content = response.dig(:result, :contents, 0, :text)
|
552
|
+
data = JSON.parse(content)
|
553
|
+
|
554
|
+
# Verify server metrics
|
555
|
+
assert_equal "test", data["server"]["name"]
|
556
|
+
assert data["server"]["uptime_seconds"] >= 0
|
557
|
+
|
558
|
+
# Verify system metrics
|
559
|
+
assert data["system"]["ruby_version"]
|
560
|
+
assert data["system"]["platform"]
|
561
|
+
|
562
|
+
# Verify process metrics
|
563
|
+
assert_equal Process.pid, data["process"]["pid"]
|
564
|
+
assert data["process"]["memory_usage_mb"] > 0
|
565
|
+
end
|
566
|
+
|
567
|
+
def test_error_handling
|
568
|
+
# Mock an error condition
|
569
|
+
resource = SystemMetrics.new
|
570
|
+
resource.define_singleton_method(:system_metrics) do
|
571
|
+
raise "Simulated error"
|
572
|
+
end
|
573
|
+
|
574
|
+
@server.register_resource_instance(resource)
|
575
|
+
|
576
|
+
response = @client.read_resource("system/metrics")
|
577
|
+
|
578
|
+
# Should still return valid JSON
|
579
|
+
assert_successful_response(response)
|
580
|
+
content = response.dig(:result, :contents, 0, :text)
|
581
|
+
data = JSON.parse(content)
|
582
|
+
|
583
|
+
# Should have minimal metrics on error
|
584
|
+
assert data["error"] || data["server"]
|
585
|
+
end
|
586
|
+
end
|
587
|
+
```
|
588
|
+
|
589
|
+
## URI Patterns and Best Practices
|
590
|
+
|
591
|
+
### Hierarchical Organization
|
592
|
+
|
593
|
+
```ruby
|
594
|
+
# Good: Clear hierarchy
|
595
|
+
"config" # Top-level config
|
596
|
+
"config/database" # Database config
|
597
|
+
"config/cache" # Cache config
|
598
|
+
"users/current" # Current user
|
599
|
+
"users/current/settings" # User settings
|
600
|
+
|
601
|
+
# Avoid: Flat structure
|
602
|
+
"database-config"
|
603
|
+
"cache-config"
|
604
|
+
"current-user"
|
605
|
+
"user-settings"
|
606
|
+
```
|
607
|
+
|
608
|
+
### Naming Conventions
|
609
|
+
|
610
|
+
```ruby
|
611
|
+
# Use lowercase with hyphens or underscores
|
612
|
+
"system/health-check" # Good
|
613
|
+
"system/health_check" # Good
|
614
|
+
"system/HealthCheck" # Avoid
|
615
|
+
"SYSTEM/HEALTH" # Avoid
|
616
|
+
|
617
|
+
# Be descriptive but concise
|
618
|
+
"api/endpoints" # Good
|
619
|
+
"api/available-endpoints-list" # Too verbose
|
620
|
+
```
|
621
|
+
|
622
|
+
### Resource Types
|
623
|
+
|
624
|
+
```ruby
|
625
|
+
# Status/Info resources
|
626
|
+
"system/status"
|
627
|
+
"server/info"
|
628
|
+
"health/check"
|
629
|
+
|
630
|
+
# Configuration resources
|
631
|
+
"config/app"
|
632
|
+
"config/features"
|
633
|
+
"settings/user"
|
634
|
+
|
635
|
+
# Data resources
|
636
|
+
"data/users"
|
637
|
+
"reports/daily"
|
638
|
+
"metrics/performance"
|
639
|
+
|
640
|
+
# Documentation resources
|
641
|
+
"docs/api"
|
642
|
+
"help/commands"
|
643
|
+
"schema/database"
|
644
|
+
```
|
645
|
+
|
646
|
+
## See Also
|
647
|
+
|
648
|
+
- [Resources Guide](../guides/resources.md)
|
649
|
+
- [Server API](server.md)
|
650
|
+
- [Testing Guide](../guides/testing.md)
|
651
|
+
- [Caching Patterns](../cookbook/caching.md)
|