resque-mcp 0.4.0 → 0.5.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: 0415f5a9bbc7dddcc1c4def5de45835d49df86158b5261567af19ece94d6d36c
4
- data.tar.gz: 6f4f8511f80a3107abd83e7e24053d0322dabb5feec212e52271bfe7bd844684
3
+ metadata.gz: aa2476b4d89a552651fd9fefbde7ebb001f75fbf684fa5182388c39cd501bcc5
4
+ data.tar.gz: bf79be53a377e49347c692bb78d2f2519e03756b63d07311bf473601414305ec
5
5
  SHA512:
6
- metadata.gz: 95a256a4a7e92257568a474254fbd052306f0e915cebad24bfc6ff237a4a678ead0ddfae17611b0f6ab0f4b2f9a195ace9cc4eda54cd0b2d5122afc0827ebdb3
7
- data.tar.gz: 2494d7df431b2b14f5a27eabc148f6a19670246afbb7051ad0144c2637773c52acaeb5d022b86e0e47d87cf691d044f6e6e1574c8640dd0389f8356189a494cf
6
+ metadata.gz: 75df4bb783023d55034a99235970952ea70f931fea145b3cebd48efa7404654c982d65a604ec29ee52086c3b3bbffae9f360eca7acc44e594a2d44a368bf9c40
7
+ data.tar.gz: a182d35074d8e72bc0f638cff1793405b37427093750810f0061f750c57f0adce34974d1e970cfcb1e4fd58f4f6663d659880bdd80f486e4fc42ecf07ae3a679
data/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.5.0]
6
+
7
+ - MCP SDK 1.x support: the `mcp` dependency widens to `>= 0.23, < 2`, admitting the SDK's API-stable 1.x line. No configuration changes; existing deployments behave as before.
8
+ - Sessionless 2026-07-28 lifecycle (SEP-2575, needs `mcp >= 1.2`): clients may skip the `initialize` handshake and carry the protocol version per request, and discover the server via `server/discover`. Older `mcp` versions keep serving the handshake lifecycle unchanged.
9
+ - Capabilities are now declared explicitly instead of taking the SDK's default, dropping the `listChanged`/`subscribe` notification flags a per-request stateless endpoint cannot push. Empty `prompts`/`resources` capabilities stay declared, so `prompts/list` and `resources/list` keep answering with empty lists.
10
+ - `subscriptions/listen` is declined with a correlatable JSON-RPC `-32601` error instead of an opaque `500`: a per-request stateless server has no notification stream to open. The refusal ships with HTTP 200 so clients that only parse successful responses still see it.
11
+
5
12
  ## [0.4.0]
6
13
 
7
14
  - Security: bump the minimum `mcp` dependency to `>= 0.23, < 1` for the DNS-rebinding fix (CVE-2026-63118); `<= 0.22` did not validate `Host`/`Origin` headers on the Streamable HTTP transport. Protection is now on by default, with loopback always allowed.
@@ -6,6 +6,8 @@ module Resque
6
6
  before_action :require_auth_token, only: :handle
7
7
 
8
8
  def handle
9
+ # Rack 3 input may not be rewindable: read before the transport does.
10
+ @raw_jsonrpc_body = request.raw_post
9
11
  server = ServerFactory.build(environment: Rails.env.to_s)
10
12
  config = Resque::Mcp.config
11
13
  # Passthrough first; our security-critical keys override, so nothing
@@ -20,21 +22,77 @@ module Resque
20
22
  transport = ::MCP::Server::Transports::StreamableHTTPTransport.new(server, **options)
21
23
 
22
24
  status, headers, body = transport.handle_request(request)
23
- headers.each { |key, value| response.set_header(key, value) }
25
+ render_transport_response(status, headers, body)
26
+ end
27
+
28
+ def method_not_allowed
29
+ head :method_not_allowed
30
+ end
31
+
32
+ private
33
+
34
+ # Rack 3 forbids these on a response; the SSE headers carry one.
35
+ HOP_BY_HOP_HEADERS = %w[
36
+ connection keep-alive proxy-authenticate proxy-authorization
37
+ te trailer transfer-encoding upgrade
38
+ ].freeze
39
+
40
+ # A callable body is a stream the transport wants to keep open (today
41
+ # only `subscriptions/listen`); stateless has nothing to push, so decline.
42
+ def render_transport_response(status, headers, body)
43
+ if body.respond_to?(:call)
44
+ # 200, not 501: clients that reject on `!response.ok` never parse the
45
+ # body, and the transport headers describe the declined stream.
46
+ return render json: {
47
+ jsonrpc: "2.0",
48
+ id: jsonrpc_request_id,
49
+ error: {
50
+ code: -32601,
51
+ message: "Method not found: #{jsonrpc_method_label} is not supported by this stateless endpoint"
52
+ }
53
+ }, status: :ok
54
+ end
55
+
56
+ headers.each do |key, value|
57
+ next if HOP_BY_HOP_HEADERS.include?(key.to_s.downcase)
58
+ response.set_header(key, value)
59
+ end
24
60
 
25
61
  payload = body.first
26
- if payload
27
- render json: payload, status: status
62
+ return head status unless payload
63
+
64
+ content_type = transport_content_type(headers)
65
+ if content_type && !content_type.start_with?("application/json")
66
+ render body: payload, content_type: content_type, status: status
28
67
  else
29
- head status
68
+ render json: payload, status: status
30
69
  end
31
70
  end
32
71
 
33
- def method_not_allowed
34
- head :method_not_allowed
72
+ # A null id leaves a strict client unable to correlate the error.
73
+ def jsonrpc_payload
74
+ @jsonrpc_payload ||= begin
75
+ parsed = JSON.parse(@raw_jsonrpc_body.to_s)
76
+ parsed.is_a?(Hash) ? parsed : {}
77
+ rescue JSON::ParserError
78
+ {}
79
+ end
35
80
  end
36
81
 
37
- private
82
+ def jsonrpc_request_id
83
+ id = jsonrpc_payload["id"]
84
+ id if id.is_a?(String) || id.is_a?(Integer)
85
+ end
86
+
87
+ def jsonrpc_method_label
88
+ method_name = jsonrpc_payload["method"]
89
+ method_name.is_a?(String) ? method_name : "this method"
90
+ end
91
+
92
+ def transport_content_type(headers)
93
+ _, value = headers.find { |key, _| key.to_s.casecmp("content-type").zero? }
94
+ value
95
+ end
38
96
 
39
97
  # No reliable boot-time hook exists (initializer ordering), so a
40
98
  # missing token is caught per request: 503, never silently open.
@@ -10,6 +10,10 @@ module Resque
10
10
  name: "resque-mcp",
11
11
  version: Resque::Mcp::VERSION,
12
12
  tools: [Tools::Overview, Tools::QueueStats, Tools::WorkerStats, Tools::ListFailures, Tools::GetFailure],
13
+ # Explicit, or the SDK advertises `listChanged`/`subscribe` streams
14
+ # we decline. This hash replaces the defaults and the SDK refuses
15
+ # methods whose key is absent — hence the empty prompts/resources.
16
+ capabilities: {tools: {}, prompts: {}, resources: {}, logging: {}},
13
17
  server_context: {adapter: Adapter.new, environment: environment}
14
18
  )
15
19
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Resque
4
4
  module Mcp
5
- VERSION = "0.4.0"
5
+ VERSION = "0.5.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.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josch Bockler
@@ -38,7 +38,7 @@ dependencies:
38
38
  version: '0.23'
39
39
  - - "<"
40
40
  - !ruby/object:Gem::Version
41
- version: '1'
41
+ version: '2'
42
42
  type: :runtime
43
43
  prerelease: false
44
44
  version_requirements: !ruby/object:Gem::Requirement
@@ -48,7 +48,7 @@ dependencies:
48
48
  version: '0.23'
49
49
  - - "<"
50
50
  - !ruby/object:Gem::Version
51
- version: '1'
51
+ version: '2'
52
52
  - !ruby/object:Gem::Dependency
53
53
  name: activesupport
54
54
  requirement: !ruby/object:Gem::Requirement