resque-mcp 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1f899cb2c9bb3c59a5185841550784e81dac79b0848da767f1cd5a81910efc84
4
- data.tar.gz: 26775e429aa90ffcf60ed7b1517f74744d7786f137890376b9263392fd1d5719
3
+ metadata.gz: a7aaa6b78cc06fac6720e3b95b382f2bc32b69d9cb8abe72ce9b0ab35c82561f
4
+ data.tar.gz: 8b908e0fdb79b0daee88070b2dd8121dad22c251328353759be555b30adf866b
5
5
  SHA512:
6
- metadata.gz: 4185dc24785f9425db9089136725a30ca45995d5dc35883dec9cbdfd0cdb2a47105037d376bc37315097f3136d90c22e2521487bd42351593593b86ea1446b0a
7
- data.tar.gz: f2e8f0558eec1d6cd83589695eddd3dca1703d65130f71530a23596f81540e3c3a3a6611cb5360cac95c3cdf97fa95bced0177c197b12ef4a58d4a82d790ff31
6
+ metadata.gz: a977ac62ae2eb47fde93099cc1583967da2186e87dc8aaa8f4bc5b613b50f70c160eb13ffa65327ca639d9a9d050e5b8561a975e39a30d34da7f85b9e1141eff
7
+ data.tar.gz: aa95121a61ce913be0702d1be6ee2d40d5ff83fbe080a1d59e8f63c30ecdc91a3f4056c832810b8dcf5922ebbf6e3a164ce6410d86959303fdb8584f349b5b14
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.2.0]
6
+
7
+ - `list_failures` tool: page through failed jobs newest-first with compact records (truncated error, args preview, no backtrace); optional `class_name` filter (filtered totals are marked `"total_note": "scan"` and paging must follow the returned `next_offset` cursor).
8
+ - `get_failure` tool: full detail for one failed job by list index — complete args (capped at a few KB), exception, backtrace capped at 30 frames with `backtrace_omitted`, worker, retry stamp. Out-of-range indexes answer with the current failure count. On the redis_multi_queue backend both failure tools require an explicit `queue` and answer a dedicated error naming the failure queues when it is missing. Failures are addressed by `index`, not `id` — the position shifts when records are removed.
9
+ - `queue_stats` tool: list all queues with sizes, or inspect one queue and page through its pending job payloads (`include_jobs: true`); paginated responses carry a `page` envelope (`total`/`offset`/`limit`/`returned`/`has_more`/`next_offset`), limits above 100 are clamped with an in-band note, and job args are shown as truncated previews.
10
+
5
11
  ## [0.1.0]
6
12
 
7
13
  - Mountable Rails engine serving a stateless MCP Streamable HTTP endpoint (`POST` only; all other verbs answer 405).
data/README.md CHANGED
@@ -27,6 +27,7 @@ mount Resque::Mcp::Engine => "/resque-mcp"
27
27
 
28
28
  # config/initializers/resque_mcp.rb
29
29
  Resque::Mcp.configure do |c|
30
+ # token could be created by e.g.: `bin/rails runner 'puts SecureRandom.base58(32)'`
30
31
  c.auth_token = Rails.application.credentials.dig(:resque_mcp, :token)
31
32
  end
32
33
  ```
@@ -44,7 +45,7 @@ Then ask, e.g., "How is my Resque doing?"
44
45
 
45
46
  ## Tools
46
47
 
47
- The tool surface is currently in progress. Every tool response returns structured JSON alongside a text body and ends in a `meta` footer naming the Rails environment and the Redis target (with any credentials stripped), so you always see what you are talking to.
48
+ The tool surface is read-only so far (worker inspection and failure retry/clear are planned). Every tool response returns structured JSON alongside a text body and ends in a `meta` footer naming the Rails environment and the Redis target (with any credentials stripped), so you always see what you are talking to.
48
49
 
49
50
  ### `overview` — read-only
50
51
 
@@ -63,10 +64,64 @@ Global Resque health snapshot; takes no parameters.
63
64
  - `processed` is Resque's lifetime counter; `failed` is the current number of records in the failed list.
64
65
  - `workers` counts registered workers, `working` those currently running a job.
65
66
 
67
+ ### `queue_stats` — read-only
68
+
69
+ List all queues with sizes, or inspect a single queue. With `queue` and `include_jobs: true` it pages through the queue's pending job payloads:
70
+
71
+ ```json
72
+ {
73
+ "queue": "imports", "size": 84,
74
+ "jobs": [
75
+ { "class": "ImportWorker", "args_preview": "[812, \"s3://bucket/batch-7.csv\"]" },
76
+ { "class": "ImportWorker", "args_preview": "[813, \"s3://bucket/batch-8.csv\"]" }
77
+ ],
78
+ "page": { "total": 84, "offset": 0, "limit": 2, "returned": 2, "has_more": true, "next_offset": 2 },
79
+ "meta": { "…": "…" }
80
+ }
81
+ ```
82
+
83
+ Every paginated response carries this `page` envelope. Limits are capped at 100 — a larger request is clamped and the clamp noted in the response.
84
+
85
+ ### `list_failures` — read-only
86
+
87
+ Page through failed jobs, newest first, as compact records (truncated error, args preview, no backtrace). Optionally filter by `class_name`.
88
+
89
+ ```json
90
+ {
91
+ "failures": [
92
+ {
93
+ "index": 1341, "failed_at": "2026/07/02 08:59:12 UTC",
94
+ "queue": "imports", "class": "ImportWorker",
95
+ "args_preview": "[812, \"s3://bucket/batch-7.csv\"]",
96
+ "exception": "PG::ConnectionBad",
97
+ "error": "could not connect to server: Connection refused… (truncated)",
98
+ "retried_at": null
99
+ }
100
+ ],
101
+ "page": { "total": 1342, "offset": 0, "limit": 20, "returned": 20, "has_more": true, "next_offset": 20 },
102
+ "meta": { "…": "…" }
103
+ }
104
+ ```
105
+
106
+ - A failure's `index` is its position in the failed list, deliberately not called an `id`: indexes shift when failures are removed, so re-list after any removal.
107
+ - Under a `class_name` filter, `next_offset` is a raw scan cursor — continue paging with the returned value, never compute `offset + limit` yourself. Filtered totals cost a full-list scan and are marked `"total_note": "scan"`.
108
+
109
+ ### `get_failure` — read-only
110
+
111
+ Full detail for one failed job by `index`: complete args, exception, full error, backtrace (capped at 30 frames, with `backtrace_omitted`), and the worker that failed it.
112
+
113
+ All truncation anywhere in the tool surface is marked in-band (`"… (truncated)"`, `backtrace_omitted`), so a model always knows when it is seeing an excerpt.
114
+
115
+ ### Failure backends
116
+
117
+ Both failure tools support Resque's `redis_multi_queue` failure backend: pass the failure-queue name as `queue`. On that backend the tools require it and answer with the list of failure queues when it is missing. On the default backend, `queue` can simply be omitted.
118
+
66
119
  ## Development
67
120
 
68
121
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake` to run the tests and standardrb. You can also run `bin/console` for an interactive prompt.
69
122
 
123
+ To release a new version, update the changelog and the version number in version.rb, commit it, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.
124
+
70
125
  ## Contributing
71
126
 
72
127
  Bug reports and pull requests are welcome on GitHub at https://github.com/jbockler/resque-mcp. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/jbockler/resque-mcp/blob/main/CODE_OF_CONDUCT.md).
@@ -4,6 +4,30 @@ module Resque
4
4
  module Mcp
5
5
  # The only code in the gem that talks to Resque. Rails-free.
6
6
  class Adapter
7
+ class UnknownQueueError < StandardError
8
+ def initialize(queue, known_queues)
9
+ known = known_queues.empty? ? "(none)" : known_queues.sort.join(", ")
10
+ super("Unknown queue #{queue.inspect}. Known queues: #{known}")
11
+ end
12
+ end
13
+
14
+ class FailureOutOfRangeError < StandardError
15
+ def initialize(index, count)
16
+ super("No failure at index #{index}. Current failure count: #{count}" \
17
+ "#{" (valid indexes 0..#{count - 1})" if count > 0}.")
18
+ end
19
+ end
20
+
21
+ class FailureQueueRequiredError < StandardError
22
+ def initialize(known_queues)
23
+ super("The redis_multi_queue failure backend is active — pass `queue` " \
24
+ "with one of the failure queues: #{known_queues.sort.join(", ")}.")
25
+ end
26
+ end
27
+
28
+ # Chunk size for the tail-backwards scan under a class_name filter.
29
+ FILTER_SCAN_CHUNK = 100
30
+
7
31
  def initialize(resque: ::Resque)
8
32
  @resque = resque
9
33
  end
@@ -23,6 +47,46 @@ module Resque
23
47
  }
24
48
  end
25
49
 
50
+ def queues
51
+ @resque.queue_sizes
52
+ end
53
+
54
+ def queue_size(queue)
55
+ ensure_known_queue!(queue)
56
+ @resque.size(queue)
57
+ end
58
+
59
+ def peek(queue, offset:, limit:)
60
+ ensure_known_queue!(queue)
61
+ {size: @resque.size(queue), jobs: to_array(@resque.peek(queue, offset, limit))}
62
+ end
63
+
64
+ # Newest-first pagination over the failed list. `offset` counts raw
65
+ # list positions back from the newest record; returned indexes are raw
66
+ # list positions (what requeue/remove take). Failures are RPUSH'd so
67
+ # index 0 is the OLDEST record, and Failure.each's 'desc' only
68
+ # reverses within a fetched slice — the newest-first window has to be
69
+ # translated to raw indexes here.
70
+ def failures(offset:, limit:, class_name: nil, queue: nil)
71
+ ensure_failure_queue!(queue)
72
+ raw_total = @resque::Failure.count(queue)
73
+ if class_name.nil?
74
+ unfiltered_failures(offset, limit, raw_total, queue)
75
+ else
76
+ filtered_failures(offset, limit, raw_total, class_name, queue)
77
+ end
78
+ end
79
+
80
+ def failure(index, queue: nil)
81
+ ensure_failure_queue!(queue)
82
+ count = @resque::Failure.count(queue)
83
+ raise FailureOutOfRangeError.new(index, count) unless index >= 0 && index < count
84
+ item = to_array(@resque::Failure.all(index, 1, queue)).first
85
+ # The list can shrink between the count read and the fetch.
86
+ raise FailureOutOfRangeError.new(index, @resque::Failure.count(queue)) if item.nil?
87
+ normalize_failure(index, item)
88
+ end
89
+
26
90
  # Resque.redis_id can embed user:password@; only this stripped form
27
91
  # may reach tool responses.
28
92
  def redis_identifier
@@ -31,6 +95,102 @@ module Resque
31
95
 
32
96
  private
33
97
 
98
+ def unfiltered_failures(offset, limit, raw_total, queue)
99
+ available = raw_total - offset
100
+ if available <= 0
101
+ return {records: [], total: raw_total, has_more: false, next_offset: nil}
102
+ end
103
+ count = [limit, available].min
104
+ raw_start = raw_total - offset - count
105
+ items = to_array(@resque::Failure.all(raw_start, count, queue))
106
+ records = items.each_with_index
107
+ .reject { |item, _i| item.nil? }
108
+ .map { |item, i| normalize_failure(raw_start + i, item) }
109
+ .reverse
110
+ has_more = offset + count < raw_total
111
+ {
112
+ records: records,
113
+ total: raw_total,
114
+ has_more: has_more,
115
+ next_offset: has_more ? offset + count : nil
116
+ }
117
+ end
118
+
119
+ # Scans the list tail-backwards in chunks collecting class_name
120
+ # matches. next_offset is a RAW cursor (raw positions consumed from
121
+ # the newest end), not a match count — page arithmetic on it is
122
+ # invalid, callers must pass it back verbatim.
123
+ def filtered_failures(offset, limit, raw_total, class_name, queue)
124
+ records = []
125
+ cursor_after_last = nil
126
+ has_more = false
127
+ pos = offset
128
+ while pos < raw_total && !has_more
129
+ chunk_size = [FILTER_SCAN_CHUNK, raw_total - pos].min
130
+ raw_start = raw_total - pos - chunk_size
131
+ items = to_array(@resque::Failure.all(raw_start, chunk_size, queue))
132
+ items.reverse_each do |item|
133
+ index = raw_total - pos - 1
134
+ pos += 1
135
+ next unless item && item.dig("payload", "class") == class_name
136
+ if records.size < limit
137
+ records << normalize_failure(index, item)
138
+ cursor_after_last = pos
139
+ else
140
+ has_more = true
141
+ break
142
+ end
143
+ end
144
+ end
145
+ {
146
+ records: records,
147
+ total: @resque::Failure.count(queue, class_name),
148
+ total_note: "scan",
149
+ has_more: has_more,
150
+ next_offset: has_more ? cursor_after_last : nil
151
+ }
152
+ end
153
+
154
+ def normalize_failure(index, item)
155
+ payload = item["payload"] || {}
156
+ {
157
+ index: index,
158
+ failed_at: item["failed_at"],
159
+ queue: item["queue"],
160
+ class: payload["class"],
161
+ args: payload["args"],
162
+ exception: item["exception"],
163
+ error: item["error"],
164
+ backtrace: item["backtrace"],
165
+ worker: item["worker"],
166
+ retried_at: item["retried_at"]
167
+ }
168
+ end
169
+
170
+ # Resque's list reads (Resque.peek, Failure.all) return a bare hash
171
+ # (or nil) instead of a list when asked for exactly one item.
172
+ def to_array(items)
173
+ items.is_a?(Array) ? items : [items].compact
174
+ end
175
+
176
+ # On redis_multi_queue, count(nil) sums ALL failure queues while
177
+ # all(..., nil) reads the empty default :failed list — pagination
178
+ # over that mismatch reports has_more forever without yielding a
179
+ # record, so a failure queue must be named explicitly.
180
+ def ensure_failure_queue!(queue)
181
+ return unless queue.nil? && multi_queue_failure_backend?
182
+ raise FailureQueueRequiredError.new(@resque::Failure.queues)
183
+ end
184
+
185
+ def multi_queue_failure_backend?
186
+ @resque::Failure.backend.name == "Resque::Failure::RedisMultiQueue"
187
+ end
188
+
189
+ def ensure_known_queue!(queue)
190
+ known = @resque.queues
191
+ raise UnknownQueueError.new(queue, known) unless known.include?(queue)
192
+ end
193
+
34
194
  # Greedy to the last "@" — passwords may contain "@", "/", or spaces;
35
195
  # over-stripping is acceptable, leaking is not.
36
196
  def strip_userinfo(id)
@@ -9,7 +9,7 @@ module Resque
9
9
  ::MCP::Server.new(
10
10
  name: "resque-mcp",
11
11
  version: Resque::Mcp::VERSION,
12
- tools: [Tools::Overview],
12
+ tools: [Tools::Overview, Tools::QueueStats, Tools::ListFailures, Tools::GetFailure],
13
13
  server_context: {adapter: Adapter.new, environment: environment}
14
14
  )
15
15
  end
@@ -3,8 +3,15 @@
3
3
  module Resque
4
4
  module Mcp
5
5
  module Tools
6
- # Shared tool helpers: adapter access, response envelopes, meta footer.
6
+ # Shared tool helpers: adapter access, response envelopes, meta footer,
7
+ # pagination and size caps (size policy lives here, not in tools).
7
8
  class Base < ::MCP::Tool
9
+ LIMIT_MAX = 100
10
+ ARGS_PREVIEW_MAX = 200
11
+ ERROR_TRUNCATE_MAX = 200
12
+ BACKTRACE_MAX_FRAMES = 30
13
+ ARGS_FULL_MAX = 4096
14
+
8
15
  class << self
9
16
  private
10
17
 
@@ -12,6 +19,50 @@ module Resque
12
19
  server_context.fetch(:adapter)
13
20
  end
14
21
 
22
+ def clamp_limit(limit)
23
+ [limit, LIMIT_MAX].min
24
+ end
25
+
26
+ # has_more/next_offset default to offset arithmetic; tools whose
27
+ # adapter owns the cursor semantics (failures) pass them in.
28
+ def page_envelope(total:, offset:, limit:, returned:, requested_limit: limit,
29
+ has_more: nil, next_offset: :compute, total_note: nil)
30
+ has_more = offset + returned < total if has_more.nil?
31
+ next_offset = has_more ? offset + returned : nil if next_offset == :compute
32
+ page = {
33
+ total: total, offset: offset, limit: limit, returned: returned,
34
+ has_more: has_more, next_offset: next_offset
35
+ }
36
+ page[:note] = "limit clamped to #{LIMIT_MAX}" if requested_limit > limit
37
+ page[:total_note] = total_note if total_note
38
+ page
39
+ end
40
+
41
+ def args_preview(args)
42
+ truncate_text(JSON.generate(args), ARGS_PREVIEW_MAX)
43
+ end
44
+
45
+ def full_args(args)
46
+ json = JSON.generate(args)
47
+ (json.length <= ARGS_FULL_MAX) ? args : truncate_text(json, ARGS_FULL_MAX)
48
+ end
49
+
50
+ def truncated_error(error)
51
+ error.nil? ? nil : truncate_text(error, ERROR_TRUNCATE_MAX)
52
+ end
53
+
54
+ # => [frames, omitted_count]
55
+ def capped_backtrace(frames)
56
+ frames = frames.is_a?(Array) ? frames : []
57
+ omitted = [frames.size - BACKTRACE_MAX_FRAMES, 0].max
58
+ [frames.first(BACKTRACE_MAX_FRAMES), omitted]
59
+ end
60
+
61
+ def truncate_text(text, max)
62
+ return text if text.length <= max
63
+ "#{text[0, max]}… (truncated)"
64
+ end
65
+
15
66
  def success_response(payload, server_context)
16
67
  body = payload.merge(meta: meta_footer(server_context))
17
68
  ::MCP::Tool::Response.new(
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Resque
4
+ module Mcp
5
+ module Tools
6
+ class GetFailure < Base
7
+ tool_name "get_failure"
8
+ description "Full detail for one failed job by list index (as returned by " \
9
+ "list_failures — indexes shift when failures are removed): complete args, " \
10
+ "exception, backtrace (capped at 30 frames), worker that failed it."
11
+ input_schema(
12
+ properties: {
13
+ index: {type: "integer", minimum: 0},
14
+ queue: {type: "string", description: "Failure queue (only relevant with the redis_multi_queue backend; use the same value as in list_failures)"}
15
+ },
16
+ required: ["index"]
17
+ )
18
+ annotations(read_only_hint: true)
19
+
20
+ def self.call(index:, server_context:, queue: nil, **)
21
+ record = adapter(server_context).failure(index, queue: queue)
22
+ backtrace, omitted = capped_backtrace(record[:backtrace])
23
+ success_response({
24
+ index: record[:index],
25
+ failed_at: record[:failed_at],
26
+ queue: record[:queue],
27
+ class: record[:class],
28
+ args: full_args(record[:args]),
29
+ exception: record[:exception],
30
+ error: record[:error],
31
+ backtrace: backtrace,
32
+ backtrace_omitted: omitted,
33
+ worker: record[:worker],
34
+ retried_at: record[:retried_at]
35
+ }, server_context)
36
+ rescue Adapter::FailureOutOfRangeError, Adapter::FailureQueueRequiredError, ArgumentError => e
37
+ error_response(e.message)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Resque
4
+ module Mcp
5
+ module Tools
6
+ class ListFailures < Base
7
+ tool_name "list_failures"
8
+ description "Page through failed jobs, newest first. Compact records without " \
9
+ "backtraces — use get_failure for full detail. Each record's `index` is its " \
10
+ "position in the failed list, not a stable id: indexes shift when failures " \
11
+ "are removed, so re-list after any removal. Continue paging with the " \
12
+ "returned page.next_offset — do not compute offsets yourself."
13
+ input_schema(
14
+ properties: {
15
+ offset: {type: "integer", default: 0, minimum: 0},
16
+ limit: {type: "integer", default: 20, minimum: 1, maximum: 100},
17
+ class_name: {type: "string", description: "Only failures of this job class"},
18
+ queue: {type: "string", description: "Failure queue (only relevant with the redis_multi_queue backend)"}
19
+ },
20
+ required: []
21
+ )
22
+ annotations(read_only_hint: true)
23
+
24
+ def self.call(server_context:, offset: 0, limit: 20, class_name: nil, queue: nil, **)
25
+ clamped = clamp_limit(limit)
26
+ result = adapter(server_context).failures(
27
+ offset: offset, limit: clamped, class_name: class_name, queue: queue
28
+ )
29
+ failures = result[:records].map do |record|
30
+ {
31
+ index: record[:index],
32
+ failed_at: record[:failed_at],
33
+ queue: record[:queue],
34
+ class: record[:class],
35
+ args_preview: args_preview(record[:args]),
36
+ exception: record[:exception],
37
+ error: truncated_error(record[:error]),
38
+ retried_at: record[:retried_at]
39
+ }
40
+ end
41
+ success_response({
42
+ failures: failures,
43
+ page: page_envelope(
44
+ total: result[:total], offset: offset, limit: clamped,
45
+ returned: failures.size, requested_limit: limit,
46
+ has_more: result[:has_more], next_offset: result[:next_offset],
47
+ total_note: result[:total_note]
48
+ )
49
+ }, server_context)
50
+ rescue Adapter::FailureQueueRequiredError, ArgumentError => e
51
+ error_response(e.message)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Resque
4
+ module Mcp
5
+ module Tools
6
+ class QueueStats < Base
7
+ tool_name "queue_stats"
8
+ description "List queues with sizes. Pass `queue` to inspect one queue and " \
9
+ "optionally page through its pending jobs (`include_jobs: true`)."
10
+ input_schema(
11
+ properties: {
12
+ queue: {type: "string", description: "Inspect a single queue by name"},
13
+ include_jobs: {type: "boolean", default: false, description: "Include pending job payloads (only with queue)"},
14
+ offset: {type: "integer", default: 0, minimum: 0},
15
+ limit: {type: "integer", default: 20, minimum: 1, maximum: 100}
16
+ },
17
+ required: []
18
+ )
19
+ annotations(read_only_hint: true)
20
+
21
+ def self.call(server_context:, queue: nil, include_jobs: false, offset: 0, limit: 20, **)
22
+ if queue.nil?
23
+ return error_response("include_jobs requires queue") if include_jobs
24
+ return success_response({queues: adapter(server_context).queues}, server_context)
25
+ end
26
+
27
+ unless include_jobs
28
+ size = adapter(server_context).queue_size(queue)
29
+ return success_response({queue: queue, size: size}, server_context)
30
+ end
31
+
32
+ clamped = clamp_limit(limit)
33
+ result = adapter(server_context).peek(queue, offset: offset, limit: clamped)
34
+ jobs = result[:jobs].map do |job|
35
+ {class: job["class"], args_preview: args_preview(job["args"])}
36
+ end
37
+ success_response({
38
+ queue: queue,
39
+ size: result[:size],
40
+ jobs: jobs,
41
+ page: page_envelope(
42
+ total: result[:size], offset: offset, limit: clamped,
43
+ returned: jobs.size, requested_limit: limit
44
+ )
45
+ }, server_context)
46
+ rescue Adapter::UnknownQueueError => e
47
+ error_response(e.message)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Resque
4
4
  module Mcp
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque-mcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josch Bockler
@@ -92,7 +92,10 @@ files:
92
92
  - lib/resque/mcp/engine.rb
93
93
  - lib/resque/mcp/server_factory.rb
94
94
  - lib/resque/mcp/tools/base.rb
95
+ - lib/resque/mcp/tools/get_failure.rb
96
+ - lib/resque/mcp/tools/list_failures.rb
95
97
  - lib/resque/mcp/tools/overview.rb
98
+ - lib/resque/mcp/tools/queue_stats.rb
96
99
  - lib/resque/mcp/version.rb
97
100
  - sig/resque/mcp.rbs
98
101
  homepage: https://github.com/jbockler/resque-mcp