status_mcp 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90ebc8381fe57ee101567bd64e18bc376d891bedbfa5082e82588494390576b3
4
- data.tar.gz: 7ac9f6100c97338d2e1db437baf9cd4a5c9c54170d449b76324a94c59f10fd81
3
+ metadata.gz: 9a0271e4490a1ed9fc6c565061e640d192db1c76dfcd2ab58b8928ff232acf95
4
+ data.tar.gz: c6dc17f1b39bd6642c2f637c404bcfa33ee71c6e83ea92b800b75e1835e61bb9
5
5
  SHA512:
6
- metadata.gz: 3a080023fd1c648d0820ebbf1701689dc8fb7fff2e6bfcb371b7a75b8b38de92d76c4aa2c67c34309a2b672cd399f4315c08ab3d06b13411c6eb35a296ab9350
7
- data.tar.gz: 10b97c76cea65e18588eff67c1874d3803113e5ca6c4559af0f35c72bbf389e1230a4caffec32c89e8df732ddc993d1fed98cfd9fcdec9121d599c6d87a6dde4
6
+ metadata.gz: 9d2fac4896efa144cbce6e096f8166aff1b5004eeb9f58d1b2ffc912b5d6191a6ac1fd2de3f771b25b160dd63d51ebe5da3d24ce0a10497a0fbc7248ffa225ba
7
+ data.tar.gz: 8f0a30f18e0a99c7371da9e0ad9425d24fad73c72a60988305d94b346551fff2fb0f97046ba1cf7781d94d9b396f51cfd30820a69e9c4b967aa1e716cb190319
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.1.1 (2026-09-08)
4
+
5
+ - Clamp `list_services` to 200 results and `fetch_status` extract length to 10000 characters
6
+ - Bound `fetch_status` HTTP hops and wall clock, and cap HTTP bodies at 1MB before they fill memory
7
+ - Fetch status pages live over HTTP; search, details, and list still use the bundled catalog
8
+ - Raise the json floor to 2.21.2 and require patched nokogiri 1.19.4 and rack 3.2.6
9
+ - Require Ruby 3.4, matching the gemspec and CI
10
+
3
11
  ## 0.1.0 (2025-11-26)
4
12
 
5
13
  - Initial release
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # status_mcp
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/status_mcp.svg)](https://badge.fury.io/rb/status_mcp) [![Test Status](https://github.com/amkisko/status_mcp.rb/actions/workflows/test.yml/badge.svg)](https://github.com/amkisko/status_mcp.rb/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/amkisko/status_mcp.rb/graph/badge.svg)](https://codecov.io/gh/amkisko/status_mcp.rb)
3
+ [![Gem Version](https://badge.fury.io/rb/status_mcp.svg)](https://badge.fury.io/rb/status_mcp) [![Test Status](https://github.com/amkisko/status_mcp.rb/actions/workflows/test.yml/badge.svg)](https://github.com/amkisko/status_mcp.rb/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/amkisko/status_mcp.rb/graph/badge.svg)](https://app.codecov.io/github/amkisko/status_mcp.rb)
4
4
 
5
5
  Ruby gem providing status page information from [awesome-status](https://github.com/amkisko/awesome-status) via MCP (Model Context Protocol) server tools. Integrates with MCP-compatible clients like Cursor IDE, Claude Desktop, and other MCP-enabled tools.
6
6
 
@@ -10,9 +10,13 @@ Sponsored by [Kisko Labs](https://www.kiskolabs.com).
10
10
  <img src="kisko.svg" width="200" alt="Sponsored by Kisko Labs" />
11
11
  </a>
12
12
 
13
+ ## Status
14
+
15
+ This project is in low-priority support / maintenance mode. Prefer [status-cli](https://github.com/amkisko/status-cli.rs) for new status-page CLI and automation work. Existing installs keep working; expect security and critical fixes only, not new features.
16
+
13
17
  ## Requirements
14
18
 
15
- - **Ruby 3.1 or higher** (Ruby 3.0 and earlier are not supported)
19
+ - **Ruby 3.4 or higher**
16
20
 
17
21
  ## Quick Start
18
22
 
@@ -81,8 +85,8 @@ The server will start and communicate via STDIN/STDOUT using the MCP protocol.
81
85
 
82
86
  - **Status Page Information**: Access to over 1700 status page links from `awesome-status`
83
87
  - **MCP Server Integration**: Ready-to-use MCP server with tools for searching and retrieving status page details
84
- - **No Authentication Required**: All data is bundled with the gem
85
- - **Offline Capable**: Once installed, the data is available locally
88
+ - **Bundled catalog**: Search, details, and list tools read local data from the gem
89
+ - **Live fetch**: `fetch_status` requests the public status URL over HTTP
86
90
 
87
91
  ## MCP Tools
88
92
 
@@ -94,8 +98,11 @@ The MCP server provides the following tools:
94
98
  2. **get_service_details** - Get detailed status links for a specific service
95
99
  - Parameters: `name` (string)
96
100
 
97
- 3. **list_services** - List all available services (limited to first 50 if too many)
98
- - Parameters: `limit` (optional integer, default: 50)
101
+ 3. **list_services** - List available services
102
+ - Parameters: `limit` (optional integer, default: 50, maximum: 200)
103
+
104
+ 4. **fetch_status** - Fetch a public status page over HTTP and extract latest status, history, and messages
105
+ - Parameters: `status_url` (string), `max_length` (optional integer, default: 10000, maximum: 10000)
99
106
 
100
107
  ## Development
101
108
 
@@ -124,6 +131,19 @@ For more information, see [CONTRIBUTING.md](CONTRIBUTING.md).
124
131
 
125
132
  If you discover a security vulnerability, please report it responsibly. See [SECURITY.md](SECURITY.md) for details.
126
133
 
134
+ ## Links
135
+
136
+ - [GitHub](https://github.com/amkisko/status_mcp.rb)
137
+ - [GitLab](https://gitlab.com/amkisko/status_mcp.rb)
138
+ - [RubyGems](https://rubygems.org/gems/status_mcp)
139
+ - [Versions Atom](https://rubygems.org/gems/status_mcp/versions.atom) (feed id `10002864981316`)
140
+ - [libraries.io](https://libraries.io/rubygems/status_mcp)
141
+ - [Deps.dev](https://deps.dev/rubygems/status_mcp)
142
+ - [SonarCloud](https://sonarcloud.io/project/overview?id=amkisko_status_mcp.rb)
143
+ - [Snyk](https://snyk.io/test/github/amkisko/status_mcp.rb)
144
+ - [Codecov](https://app.codecov.io/github/amkisko/status_mcp.rb)
145
+ - [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/amkisko/status_mcp.rb)
146
+
127
147
  ## License
128
148
 
129
149
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StatusMcp
4
+ class FetchBudget
5
+ class ExhaustedError < Error; end
6
+
7
+ def self.monotonic_now
8
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
9
+ end
10
+
11
+ def initialize(max_requests:, deadline_seconds:)
12
+ @remaining = max_requests
13
+ @deadline_at = self.class.monotonic_now + deadline_seconds
14
+ end
15
+
16
+ def consume!
17
+ raise ExhaustedError, "Fetch hop budget exceeded" if @remaining <= 0
18
+ if self.class.monotonic_now >= @deadline_at
19
+ raise ExhaustedError, "Fetch deadline exceeded"
20
+ end
21
+
22
+ @remaining -= 1
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ipaddr"
4
+ require "socket"
5
+ require "uri"
6
+
7
+ module StatusMcp
8
+ class NetworkPolicy
9
+ ALLOWED_SCHEMES = %w[http https].freeze
10
+ FORBIDDEN_RANGES = %w[
11
+ 0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8
12
+ 169.254.0.0/16 172.16.0.0/12 192.0.0.0/24 192.0.2.0/24
13
+ 192.88.99.0/24 192.168.0.0/16 198.18.0.0/15 198.51.100.0/24
14
+ 203.0.113.0/24 224.0.0.0/4 240.0.0.0/4
15
+ ::/128 ::1/128 64:ff9b::/96 100::/64 2001::/23 2002::/16
16
+ fc00::/7 fe80::/10 ff00::/8
17
+ ].map { |range| IPAddr.new(range) }.freeze
18
+
19
+ def self.validate!(url)
20
+ uri = URI.parse(url.to_s)
21
+ reject! unless ALLOWED_SCHEMES.include?(uri.scheme&.downcase)
22
+ reject! if uri.host.to_s.empty? || uri.userinfo
23
+
24
+ [uri, allowed_ip_address(uri)]
25
+ rescue URI::InvalidURIError
26
+ reject!
27
+ end
28
+
29
+ def self.allowed_ip_address(uri)
30
+ addresses = Addrinfo.getaddrinfo(uri.host, uri.port, nil, :STREAM)
31
+ .map(&:ip_address)
32
+ .uniq
33
+ reject! if addresses.empty? || addresses.any? { |address| forbidden?(address) }
34
+
35
+ addresses.first
36
+ rescue SocketError, SystemCallError
37
+ reject!
38
+ end
39
+ private_class_method :allowed_ip_address
40
+
41
+ def self.forbidden?(address)
42
+ parsed = IPAddr.new(address.split("%", 2).first)
43
+ parsed = parsed.native if parsed.ipv4_mapped?
44
+ FORBIDDEN_RANGES.any? { |range| range.include?(parsed) }
45
+ rescue IPAddr::InvalidAddressError
46
+ true
47
+ end
48
+ private_class_method :forbidden?
49
+
50
+ def self.reject!
51
+ raise UnsafeUrlError, "URL destination is not allowed"
52
+ end
53
+ private_class_method :reject!
54
+ end
55
+ end
@@ -7,6 +7,8 @@ require "uri"
7
7
  require "openssl"
8
8
  require "nokogiri"
9
9
  require_relative "../status_mcp"
10
+ require_relative "network_policy"
11
+ require_relative "fetch_budget"
10
12
 
11
13
  module StatusMcp
12
14
  class Server
@@ -191,16 +193,18 @@ module StatusMcp
191
193
  end
192
194
 
193
195
  class ListServicesTool < BaseTool
196
+ MAX_LIST_LIMIT = 200
197
+
194
198
  tool_name "list_services"
195
- description "List all available services (limited to first 50 if too many)"
199
+ description "List available services. Default 50. Maximum 200."
196
200
 
197
201
  arguments do
198
- optional(:limit).filled(:integer).description("Limit number of results (default 50)")
202
+ optional(:limit).filled(:integer).description("Limit number of results (default 50, maximum 200)")
199
203
  end
200
204
 
201
205
  def call(limit: 50)
202
206
  services = load_data
203
- limit ||= 50
207
+ limit = (limit || 50).clamp(1, MAX_LIST_LIMIT)
204
208
 
205
209
  list = services.take(limit).map { |s| s["name"] }
206
210
 
@@ -218,16 +222,27 @@ module StatusMcp
218
222
  class FetchStatusTool < BaseTool
219
223
  # Maximum response size (1MB) to protect against zip bombs and crawler protection pages
220
224
  MAX_RESPONSE_SIZE = 1 * 1024 * 1024 # 1MB
225
+ MAX_EXTRACT_LENGTH = 10_000
226
+ # Two common feed paths plus main, history, and a few redirects. Raise MAX_FETCH_REQUESTS if a vendor needs a longer hop chain.
227
+ MAX_FEED_PROBES = 2
228
+ MAX_FETCH_REQUESTS = 6
229
+ FETCH_DEADLINE_SECONDS = 20
221
230
 
222
231
  tool_name "fetch_status"
223
232
  description "Fetch status from a status_url with HTML purification. Extracts latest status, history, and messages from status pages."
224
233
 
225
234
  arguments do
226
235
  required(:status_url).filled(:string).description("Status page URL to fetch")
227
- optional(:max_length).filled(:integer).description("Maximum length of extracted text in characters (default: 10000)")
236
+ optional(:max_length).filled(:integer).description("Maximum length of extracted text in characters (default: 10000, maximum: 10000)")
228
237
  end
229
238
 
230
239
  def call(status_url:, max_length: 10000)
240
+ max_length = (max_length || MAX_EXTRACT_LENGTH).clamp(1, MAX_EXTRACT_LENGTH)
241
+ @fetch_budget = StatusMcp::FetchBudget.new(
242
+ max_requests: MAX_FETCH_REQUESTS,
243
+ deadline_seconds: FETCH_DEADLINE_SECONDS
244
+ )
245
+
231
246
  # Try incident.io API first (only if we detect it's an incident.io page)
232
247
  api_info = nil
233
248
  api_url = nil
@@ -242,7 +257,11 @@ module StatusMcp
242
257
  api_info = nil
243
258
  end
244
259
  end
245
- rescue => e
260
+ rescue StatusMcp::UnsafeUrlError, StatusMcp::ResponseSizeExceededError
261
+ raise
262
+ rescue StatusMcp::FetchBudget::ExhaustedError
263
+ api_info = nil
264
+ rescue
246
265
  # Not an incident.io page or API failed, continue with other methods
247
266
  end
248
267
  end
@@ -259,8 +278,11 @@ module StatusMcp
259
278
  successful_feed_url = feed_url
260
279
  break
261
280
  end
262
- rescue => e
263
- # Try next feed URL
281
+ rescue StatusMcp::UnsafeUrlError, StatusMcp::ResponseSizeExceededError
282
+ raise
283
+ rescue StatusMcp::FetchBudget::ExhaustedError
284
+ break
285
+ rescue
264
286
  next
265
287
  end
266
288
  end
@@ -269,8 +291,11 @@ module StatusMcp
269
291
  main_info = nil
270
292
  begin
271
293
  main_info = fetch_and_extract(status_url, max_length)
272
- rescue => e
273
- # If we have feed info, that's okay
294
+ rescue StatusMcp::UnsafeUrlError, StatusMcp::ResponseSizeExceededError
295
+ raise
296
+ rescue StatusMcp::FetchBudget::ExhaustedError
297
+ main_info = {latest_status: nil, history: [], messages: [], error: nil} unless feed_info || api_info
298
+ rescue
274
299
  main_info = {latest_status: nil, history: [], messages: [], error: nil} unless feed_info
275
300
  end
276
301
 
@@ -281,9 +306,12 @@ module StatusMcp
281
306
  if history_url && history_url != status_url
282
307
  begin
283
308
  history_info = fetch_and_extract(history_url, max_length, history_only: true)
284
- rescue => e
285
- # Silently fail if history page doesn't exist or has errors
286
- # This is expected for many status pages
309
+ rescue StatusMcp::UnsafeUrlError, StatusMcp::ResponseSizeExceededError
310
+ raise
311
+ rescue StatusMcp::FetchBudget::ExhaustedError
312
+ history_info = nil
313
+ rescue
314
+ # History page missing is common
287
315
  end
288
316
  end
289
317
 
@@ -348,6 +376,8 @@ module StatusMcp
348
376
  rescue => e
349
377
  error_message = if e.is_a?(StatusMcp::ResponseSizeExceededError)
350
378
  "Response size limit exceeded: #{e.message}"
379
+ elsif e.is_a?(StatusMcp::UnsafeUrlError)
380
+ e.message
351
381
  else
352
382
  "Error fetching status: #{e.message}"
353
383
  end
@@ -366,8 +396,11 @@ module StatusMcp
366
396
  redirect_count = 0
367
397
 
368
398
  while redirect_count < max_redirects
369
- uri = URI(current_url)
399
+ @fetch_budget&.consume!
400
+
401
+ uri, ip_address = NetworkPolicy.validate!(current_url)
370
402
  http = Net::HTTP.new(uri.host, uri.port)
403
+ http.ipaddr = ip_address
371
404
  http.use_ssl = (uri.scheme == "https")
372
405
  if http.use_ssl?
373
406
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER
@@ -380,43 +413,52 @@ module StatusMcp
380
413
  request["User-Agent"] = "Mozilla/5.0 (compatible; StatusMcp/1.0)"
381
414
  request["Accept"] = accept
382
415
 
383
- response = http.request(request)
384
-
385
- # Handle redirects (301, 302, 307, 308)
386
- if response.is_a?(Net::HTTPRedirection) && response["location"]
387
- redirect_count += 1
388
- location = response["location"]
389
- # Handle relative redirects
390
- current_url = URI.join(current_url, location).to_s
391
- next
392
- end
393
-
394
- # Check response size before returning (protect against zip bombs and crawler protection)
395
- if response.is_a?(Net::HTTPSuccess)
396
- # Check Content-Length header first if available (optimization to avoid reading large bodies)
397
- content_length = response["Content-Length"]
398
- if content_length
399
- content_length_int = content_length.to_i
400
- if content_length_int > MAX_RESPONSE_SIZE
401
- raise StatusMcp::ResponseSizeExceededError.new(content_length_int, MAX_RESPONSE_SIZE, uri: uri.to_s)
402
- end
403
- end
404
-
405
- # Read body and check actual size (Content-Length might be missing or incorrect)
406
- response_body = response.body || ""
407
- response_size = response_body.bytesize
408
- if response_size > MAX_RESPONSE_SIZE
409
- raise StatusMcp::ResponseSizeExceededError.new(response_size, MAX_RESPONSE_SIZE, uri: uri.to_s)
416
+ redirected = false
417
+ captured = nil
418
+ http.request(request) do |response|
419
+ if response.is_a?(Net::HTTPRedirection) && response["location"]
420
+ drain_body(response)
421
+ redirect_count += 1
422
+ current_url = URI.join(current_url, response["location"]).to_s
423
+ redirected = true
424
+ else
425
+ attach_capped_body!(response, uri)
426
+ captured = response
410
427
  end
411
428
  end
412
429
 
413
- return response
430
+ next if redirected
431
+ return captured
414
432
  end
415
433
 
416
- # Too many redirects
417
434
  raise "Too many redirects (max: #{max_redirects})"
418
435
  end
419
436
 
437
+ def drain_body(response)
438
+ response.read_body { |_| }
439
+ end
440
+
441
+ def attach_capped_body!(response, uri)
442
+ content_length = response["Content-Length"]
443
+ if content_length
444
+ content_length_int = content_length.to_i
445
+ if content_length_int > MAX_RESPONSE_SIZE
446
+ drain_body(response)
447
+ raise StatusMcp::ResponseSizeExceededError.new(content_length_int, MAX_RESPONSE_SIZE, uri: uri.to_s)
448
+ end
449
+ end
450
+
451
+ buffer = +""
452
+ response.read_body do |chunk|
453
+ next_size = buffer.bytesize + chunk.bytesize
454
+ if next_size > MAX_RESPONSE_SIZE
455
+ raise StatusMcp::ResponseSizeExceededError.new(next_size, MAX_RESPONSE_SIZE, uri: uri.to_s)
456
+ end
457
+ buffer << chunk
458
+ end
459
+ response.define_singleton_method(:body) { buffer }
460
+ end
461
+
420
462
  def fetch_and_extract(url, max_length, history_only: false)
421
463
  response = fetch_with_redirects(url)
422
464
  http_status_code = response.code.to_i
@@ -523,8 +565,7 @@ module StatusMcp
523
565
 
524
566
  # Parse JSON response
525
567
  parse_incident_io_api(json_body, max_length)
526
- rescue StatusMcp::ResponseSizeExceededError
527
- # Re-raise response size errors
568
+ rescue StatusMcp::ResponseSizeExceededError, StatusMcp::UnsafeUrlError, StatusMcp::FetchBudget::ExhaustedError
528
569
  raise
529
570
  rescue JSON::ParserError => e
530
571
  {
@@ -644,7 +685,7 @@ module StatusMcp
644
685
  uri = URI(status_url)
645
686
  base_path = uri.path.chomp("/")
646
687
 
647
- # Common RSS/Atom feed patterns
688
+ # Common RSS/Atom feed patterns. MAX_FEED_PROBES is the known ceiling.
648
689
  feed_patterns = [
649
690
  "/feed.rss",
650
691
  "/feed.atom",
@@ -653,7 +694,7 @@ module StatusMcp
653
694
  "/feed",
654
695
  "/status.rss",
655
696
  "/status.atom"
656
- ]
697
+ ].first(MAX_FEED_PROBES)
657
698
 
658
699
  feed_urls = []
659
700
  feed_patterns.each do |pattern|
@@ -686,8 +727,7 @@ module StatusMcp
686
727
 
687
728
  # Parse RSS/Atom feed
688
729
  parse_feed(feed_body, max_length)
689
- rescue StatusMcp::ResponseSizeExceededError
690
- # Re-raise response size errors
730
+ rescue StatusMcp::ResponseSizeExceededError, StatusMcp::UnsafeUrlError, StatusMcp::FetchBudget::ExhaustedError
691
731
  raise
692
732
  rescue => e
693
733
  {
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StatusMcp
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
data/lib/status_mcp.rb CHANGED
@@ -6,6 +6,7 @@ require_relative "status_mcp/version"
6
6
 
7
7
  module StatusMcp
8
8
  class Error < StandardError; end
9
+ class UnsafeUrlError < Error; end
9
10
 
10
11
  class ResponseSizeExceededError < Error
11
12
  attr_reader :size, :max_size, :uri
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: status_mcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Makarov
@@ -30,6 +30,9 @@ dependencies:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '1.18'
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 1.19.4
33
36
  type: :runtime
34
37
  prerelease: false
35
38
  version_requirements: !ruby/object:Gem::Requirement
@@ -37,6 +40,9 @@ dependencies:
37
40
  - - "~>"
38
41
  - !ruby/object:Gem::Version
39
42
  version: '1.18'
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: 1.19.4
40
46
  - !ruby/object:Gem::Dependency
41
47
  name: rack
42
48
  requirement: !ruby/object:Gem::Requirement
@@ -44,6 +50,9 @@ dependencies:
44
50
  - - "~>"
45
51
  - !ruby/object:Gem::Version
46
52
  version: '3.2'
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: 3.2.6
47
56
  type: :runtime
48
57
  prerelease: false
49
58
  version_requirements: !ruby/object:Gem::Requirement
@@ -51,6 +60,9 @@ dependencies:
51
60
  - - "~>"
52
61
  - !ruby/object:Gem::Version
53
62
  version: '3.2'
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: 3.2.6
54
66
  - !ruby/object:Gem::Dependency
55
67
  name: base64
56
68
  requirement: !ruby/object:Gem::Requirement
@@ -65,6 +77,20 @@ dependencies:
65
77
  - - "~>"
66
78
  - !ruby/object:Gem::Version
67
79
  version: '0.3'
80
+ - !ruby/object:Gem::Dependency
81
+ name: json
82
+ requirement: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: 2.21.2
87
+ type: :runtime
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: 2.21.2
68
94
  - !ruby/object:Gem::Dependency
69
95
  name: rspec
70
96
  requirement: !ruby/object:Gem::Requirement
@@ -79,6 +105,20 @@ dependencies:
79
105
  - - "~>"
80
106
  - !ruby/object:Gem::Version
81
107
  version: '3.13'
108
+ - !ruby/object:Gem::Dependency
109
+ name: polyrun
110
+ requirement: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: 2.2.0
115
+ type: :development
116
+ prerelease: false
117
+ version_requirements: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: 2.2.0
82
122
  - !ruby/object:Gem::Dependency
83
123
  name: webmock
84
124
  requirement: !ruby/object:Gem::Requirement
@@ -100,6 +140,9 @@ dependencies:
100
140
  - - "~>"
101
141
  - !ruby/object:Gem::Version
102
142
  version: '6.3'
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: 6.4.0
103
146
  type: :development
104
147
  prerelease: false
105
148
  version_requirements: !ruby/object:Gem::Requirement
@@ -107,6 +150,9 @@ dependencies:
107
150
  - - "~>"
108
151
  - !ruby/object:Gem::Version
109
152
  version: '6.3'
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: 6.4.0
110
156
  - !ruby/object:Gem::Dependency
111
157
  name: rake
112
158
  requirement: !ruby/object:Gem::Requirement
@@ -177,6 +223,76 @@ dependencies:
177
223
  - - "~>"
178
224
  - !ruby/object:Gem::Version
179
225
  version: '1.52'
226
+ - !ruby/object:Gem::Dependency
227
+ name: standard-custom
228
+ requirement: !ruby/object:Gem::Requirement
229
+ requirements:
230
+ - - "~>"
231
+ - !ruby/object:Gem::Version
232
+ version: '1.0'
233
+ type: :development
234
+ prerelease: false
235
+ version_requirements: !ruby/object:Gem::Requirement
236
+ requirements:
237
+ - - "~>"
238
+ - !ruby/object:Gem::Version
239
+ version: '1.0'
240
+ - !ruby/object:Gem::Dependency
241
+ name: standard-performance
242
+ requirement: !ruby/object:Gem::Requirement
243
+ requirements:
244
+ - - "~>"
245
+ - !ruby/object:Gem::Version
246
+ version: '1.8'
247
+ type: :development
248
+ prerelease: false
249
+ version_requirements: !ruby/object:Gem::Requirement
250
+ requirements:
251
+ - - "~>"
252
+ - !ruby/object:Gem::Version
253
+ version: '1.8'
254
+ - !ruby/object:Gem::Dependency
255
+ name: standard-rspec
256
+ requirement: !ruby/object:Gem::Requirement
257
+ requirements:
258
+ - - "~>"
259
+ - !ruby/object:Gem::Version
260
+ version: '0.3'
261
+ type: :development
262
+ prerelease: false
263
+ version_requirements: !ruby/object:Gem::Requirement
264
+ requirements:
265
+ - - "~>"
266
+ - !ruby/object:Gem::Version
267
+ version: '0.3'
268
+ - !ruby/object:Gem::Dependency
269
+ name: rubocop-rspec
270
+ requirement: !ruby/object:Gem::Requirement
271
+ requirements:
272
+ - - "~>"
273
+ - !ruby/object:Gem::Version
274
+ version: '3.8'
275
+ type: :development
276
+ prerelease: false
277
+ version_requirements: !ruby/object:Gem::Requirement
278
+ requirements:
279
+ - - "~>"
280
+ - !ruby/object:Gem::Version
281
+ version: '3.8'
282
+ - !ruby/object:Gem::Dependency
283
+ name: rubocop-thread_safety
284
+ requirement: !ruby/object:Gem::Requirement
285
+ requirements:
286
+ - - "~>"
287
+ - !ruby/object:Gem::Version
288
+ version: '0.7'
289
+ type: :development
290
+ prerelease: false
291
+ version_requirements: !ruby/object:Gem::Requirement
292
+ requirements:
293
+ - - "~>"
294
+ - !ruby/object:Gem::Version
295
+ version: '0.7'
180
296
  - !ruby/object:Gem::Dependency
181
297
  name: appraisal
182
298
  requirement: !ruby/object:Gem::Requirement
@@ -234,6 +350,8 @@ files:
234
350
  - assets/data.json
235
351
  - bin/status_mcp
236
352
  - lib/status_mcp.rb
353
+ - lib/status_mcp/fetch_budget.rb
354
+ - lib/status_mcp/network_policy.rb
237
355
  - lib/status_mcp/server.rb
238
356
  - lib/status_mcp/version.rb
239
357
  homepage: https://github.com/amkisko/status_mcp.rb
@@ -247,14 +365,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
247
365
  requirements:
248
366
  - - ">="
249
367
  - !ruby/object:Gem::Version
250
- version: '3.1'
368
+ version: '3.4'
251
369
  required_rubygems_version: !ruby/object:Gem::Requirement
252
370
  requirements:
253
371
  - - ">="
254
372
  - !ruby/object:Gem::Version
255
373
  version: '0'
256
374
  requirements: []
257
- rubygems_version: 3.6.9
375
+ rubygems_version: 4.0.6
258
376
  specification_version: 4
259
377
  summary: Status MCP (Model Context Protocol) server for status page information
260
378
  test_files: []