airbrake_mcp 1.0.3 → 1.0.5

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: 722e5cf91d2f23f4723ce52aecf454c9e4dd6371e6388dbcd9d2dfb4703cf9f8
4
- data.tar.gz: df24d6787c6ee9abb46783e58c87cc4b7c56a6999db39b657ee556099f7ca674
3
+ metadata.gz: e285107444c59dad9e1bf190b5533358d3b31d2d87555ab5b95e1973dce12bd9
4
+ data.tar.gz: d292168104b253d786fd1a735da199b77debbcfcbf8bb147f730d09daff28ff1
5
5
  SHA512:
6
- metadata.gz: e078d5b0fb0435dd004ddb9ddf8654ac705a265469ea19025a8a01e7b9ea5cc1b6fde09ba457887efbafa54bf3f7386962ccd61497db9cc910f4bcbe77184b7b
7
- data.tar.gz: 9fd4a9aefc16035ef2c0633a54a5083c6c0e2100fe3a6fb695cae8c3218dbcf6a0862fb5adde30269c0d5885afa2ea0bf8a31bef3eed6ad1b42f04587611dc9c
6
+ metadata.gz: fe0f6286ba31d60bd4db9d2597b9f30bed32cb9b4e1a113d9fcb46c906b51e8e3b8e9eab93ce7edfcd39486b8c5171724f8d1079e94a4d20d6e486b1db76ffc9
7
+ data.tar.gz: 43264b5a7a02d0d033fa3b25a97e59c3273ed473133107431639747a33be0922290911a29abd07b1d9050915c3df46b3f9dff54019cabe57f342844b75d382c2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.4] - 2026-01-06
4
+
5
+ ### Fixed
6
+ - Fixed API URL path construction (removed leading slashes causing requests to bypass /api/v4 path)
7
+ - Fixed MCP response serialization (workaround for MCP gem bug where Content objects aren't converted to hashes)
8
+
3
9
  ## [1.0.3] - 2026-01-06
4
10
 
5
11
  ### Fixed
data/README.md CHANGED
@@ -142,12 +142,82 @@ Parameters:
142
142
  bundle exec rspec
143
143
  ```
144
144
 
145
- ### Building the Gem
145
+ ### Building and Installing from Source
146
146
 
147
147
  ```bash
148
+ # Build the gem
148
149
  gem build airbrake_mcp.gemspec
150
+
151
+ # Install locally
152
+ gem install ./airbrake_mcp-*.gem --no-document
153
+
154
+ # If using rbenv, rehash to update shims
155
+ rbenv rehash
156
+ ```
157
+
158
+ ### Updating an Installed Gem
159
+
160
+ When updating the gem after code changes:
161
+
162
+ ```bash
163
+ # 1. Remove ALL old versions
164
+ gem uninstall airbrake_mcp --all --executables --ignore-dependencies
165
+
166
+ # 2. Remove old gem files
167
+ rm -f *.gem
168
+
169
+ # 3. Build fresh gem
170
+ gem build airbrake_mcp.gemspec
171
+
172
+ # 4. Install with force flag
173
+ gem install ./airbrake_mcp-*.gem --no-document --force
174
+
175
+ # 5. Rehash rbenv shims
176
+ rbenv rehash
177
+
178
+ # 6. Verify installation
179
+ gem which airbrake_mcp
149
180
  ```
150
181
 
182
+ ## Troubleshooting
183
+
184
+ ### "command not found" after installation
185
+
186
+ If you see `rbenv: airbrake_mcp: command not found`, the gem was installed for a different Ruby version.
187
+
188
+ ```bash
189
+ # Check which Ruby versions have the gem
190
+ rbenv versions # shows available versions
191
+ gem list airbrake_mcp # shows if installed for current Ruby
192
+
193
+ # Install for the correct Ruby version
194
+ RBENV_VERSION=3.4.4 gem install ./airbrake_mcp-*.gem --no-document
195
+ rbenv rehash
196
+ ```
197
+
198
+ ### MCP fails to connect after gem update
199
+
200
+ 1. Ensure the gem is completely uninstalled before reinstalling:
201
+ ```bash
202
+ gem uninstall airbrake_mcp --all --executables --ignore-dependencies
203
+ ```
204
+
205
+ 2. Verify the new code is in the built gem:
206
+ ```bash
207
+ gem unpack airbrake_mcp-*.gem --target=/tmp/check
208
+ cat /tmp/check/airbrake_mcp-*/lib/airbrake_mcp/client.rb
209
+ rm -rf /tmp/check
210
+ ```
211
+
212
+ 3. After installation, verify the installed code:
213
+ ```bash
214
+ cat $(dirname $(gem which airbrake_mcp))/airbrake_mcp/client.rb
215
+ ```
216
+
217
+ ### Large Error Group IDs
218
+
219
+ Airbrake uses very large integers for error group IDs (e.g., `4235892606830408977`). These IDs are passed as strings to avoid JavaScript precision loss issues. When using tools like `get_error` or `list_notices`, provide the `group_id` exactly as shown in `list_errors` output.
220
+
151
221
  ## License
152
222
 
153
223
  MIT
@@ -15,36 +15,36 @@ module AirbrakeMcp
15
15
  attr_reader :project_id
16
16
 
17
17
  def projects
18
- get('/projects')['projects']
18
+ get('projects')['projects']
19
19
  end
20
20
 
21
21
  def groups(project_id: @project_id, page: 1, limit: 20)
22
- get("/projects/#{project_id}/groups", { page: page, limit: limit })
22
+ get("projects/#{project_id}/groups", { page: page, limit: limit })
23
23
  end
24
24
 
25
25
  def group(group_id, project_id: @project_id)
26
- get("/projects/#{project_id}/groups/#{group_id}")
26
+ get("projects/#{project_id}/groups/#{group_id}")['group']
27
27
  end
28
28
 
29
29
  def notices(group_id, project_id: @project_id, page: 1, limit: 10)
30
- get("/projects/#{project_id}/groups/#{group_id}/notices", { page: page, limit: limit })
30
+ get("projects/#{project_id}/groups/#{group_id}/notices", { page: page, limit: limit })
31
31
  end
32
32
 
33
33
  # Write operations
34
34
  def resolve_group(group_id, project_id: @project_id)
35
- put("/projects/#{project_id}/groups/#{group_id}/resolved")
35
+ put("projects/#{project_id}/groups/#{group_id}/resolved")
36
36
  end
37
37
 
38
38
  def unresolve_group(group_id, project_id: @project_id)
39
- put("/projects/#{project_id}/groups/#{group_id}/unresolved")
39
+ put("projects/#{project_id}/groups/#{group_id}/unresolved")
40
40
  end
41
41
 
42
42
  def mute_group(group_id, project_id: @project_id)
43
- put("/projects/#{project_id}/groups/#{group_id}/muted")
43
+ put("projects/#{project_id}/groups/#{group_id}/muted")
44
44
  end
45
45
 
46
46
  def unmute_group(group_id, project_id: @project_id)
47
- put("/projects/#{project_id}/groups/#{group_id}/unmuted")
47
+ put("projects/#{project_id}/groups/#{group_id}/unmuted")
48
48
  end
49
49
 
50
50
  private
@@ -29,7 +29,7 @@ module AirbrakeMcp
29
29
  muted = group['muted'] ? ' (muted)' : ''
30
30
  error = group['errors']&.first || {}
31
31
 
32
- output += "#{status}#{muted} ##{group['id']}\n"
32
+ output += "#{status}#{muted} id:#{group['id']}\n"
33
33
  output += " Type: #{error['type']}\n"
34
34
  output += " Message: #{truncate(error['message'], 100)}\n"
35
35
  output += " Count: #{group['noticeCount']} occurrences\n"
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AirbrakeMcp
4
+ module ResponseHelper
5
+ # Workaround for MCP gem bug where Content objects aren't converted to hashes
6
+ # in Response#to_h. This creates responses with pre-converted content.
7
+ def self.text_response(text, error: false)
8
+ MCP::Tool::Response.new([{ type: "text", text: text }], error: error)
9
+ end
10
+ end
11
+ end
@@ -8,7 +8,7 @@ module AirbrakeMcp
8
8
 
9
9
  input_schema(
10
10
  properties: {
11
- group_id: { type: "integer", description: "Error group ID" },
11
+ group_id: { type: "string", description: "Error group ID" },
12
12
  project_id: { type: "integer", description: "Project ID (uses default if not specified)" }
13
13
  },
14
14
  required: ["group_id"]
@@ -19,24 +19,16 @@ module AirbrakeMcp
19
19
  pid = project_id || client.project_id
20
20
 
21
21
  unless pid
22
- return MCP::Tool::Response.new([
23
- MCP::Content::Text.new("Error: No project_id specified and no default configured")
24
- ], error: true)
22
+ return ResponseHelper.text_response("Error: No project_id specified and no default configured", error: true)
25
23
  end
26
24
 
27
25
  group = client.group(group_id, project_id: pid)
28
26
 
29
- MCP::Tool::Response.new([
30
- MCP::Content::Text.new(Formatters.format_group_detail(group))
31
- ])
27
+ ResponseHelper.text_response(Formatters.format_group_detail(group))
32
28
  rescue Client::NotFoundError
33
- MCP::Tool::Response.new([
34
- MCP::Content::Text.new("Error: Error group #{group_id} not found")
35
- ], error: true)
29
+ ResponseHelper.text_response("Error: Error group #{group_id} not found", error: true)
36
30
  rescue Client::ApiError => e
37
- MCP::Tool::Response.new([
38
- MCP::Content::Text.new("Error: #{e.message}")
39
- ], error: true)
31
+ ResponseHelper.text_response("Error: #{e.message}", error: true)
40
32
  end
41
33
  end
42
34
  end
@@ -21,9 +21,7 @@ module AirbrakeMcp
21
21
  pid = project_id || client.project_id
22
22
 
23
23
  unless pid
24
- return MCP::Tool::Response.new([
25
- MCP::Content::Text.new("Error: No project_id specified and no default configured")
26
- ], error: true)
24
+ return ResponseHelper.text_response("Error: No project_id specified and no default configured", error: true)
27
25
  end
28
26
 
29
27
  result = client.groups(project_id: pid, page: page, limit: limit)
@@ -33,13 +31,9 @@ module AirbrakeMcp
33
31
  result['groups'] = result['groups'].select { |g| g['resolved'] == resolved }
34
32
  end
35
33
 
36
- MCP::Tool::Response.new([
37
- MCP::Content::Text.new(Formatters.format_groups(result))
38
- ])
34
+ ResponseHelper.text_response(Formatters.format_groups(result))
39
35
  rescue Client::ApiError => e
40
- MCP::Tool::Response.new([
41
- MCP::Content::Text.new("Error: #{e.message}")
42
- ], error: true)
36
+ ResponseHelper.text_response("Error: #{e.message}", error: true)
43
37
  end
44
38
  end
45
39
  end
@@ -8,7 +8,7 @@ module AirbrakeMcp
8
8
 
9
9
  input_schema(
10
10
  properties: {
11
- group_id: { type: "integer", description: "Error group ID" },
11
+ group_id: { type: "string", description: "Error group ID" },
12
12
  project_id: { type: "integer", description: "Project ID (uses default if not specified)" },
13
13
  page: { type: "integer", description: "Page number (default: 1)" },
14
14
  limit: { type: "integer", description: "Results per page (default: 10)" }
@@ -21,24 +21,16 @@ module AirbrakeMcp
21
21
  pid = project_id || client.project_id
22
22
 
23
23
  unless pid
24
- return MCP::Tool::Response.new([
25
- MCP::Content::Text.new("Error: No project_id specified and no default configured")
26
- ], error: true)
24
+ return ResponseHelper.text_response("Error: No project_id specified and no default configured", error: true)
27
25
  end
28
26
 
29
27
  result = client.notices(group_id, project_id: pid, page: page, limit: limit)
30
28
 
31
- MCP::Tool::Response.new([
32
- MCP::Content::Text.new(Formatters.format_notices(result))
33
- ])
29
+ ResponseHelper.text_response(Formatters.format_notices(result))
34
30
  rescue Client::NotFoundError
35
- MCP::Tool::Response.new([
36
- MCP::Content::Text.new("Error: Error group #{group_id} not found")
37
- ], error: true)
31
+ ResponseHelper.text_response("Error: Error group #{group_id} not found", error: true)
38
32
  rescue Client::ApiError => e
39
- MCP::Tool::Response.new([
40
- MCP::Content::Text.new("Error: #{e.message}")
41
- ], error: true)
33
+ ResponseHelper.text_response("Error: #{e.message}", error: true)
42
34
  end
43
35
  end
44
36
  end
@@ -10,13 +10,9 @@ module AirbrakeMcp
10
10
  client = server_context[:client]
11
11
  projects = client.projects
12
12
 
13
- MCP::Tool::Response.new([
14
- MCP::Content::Text.new(Formatters.format_projects(projects))
15
- ])
13
+ ResponseHelper.text_response(Formatters.format_projects(projects))
16
14
  rescue Client::ApiError => e
17
- MCP::Tool::Response.new([
18
- MCP::Content::Text.new("Error: #{e.message}")
19
- ], error: true)
15
+ ResponseHelper.text_response("Error: #{e.message}", error: true)
20
16
  end
21
17
  end
22
18
  end
@@ -8,7 +8,7 @@ module AirbrakeMcp
8
8
 
9
9
  input_schema(
10
10
  properties: {
11
- group_id: { type: "integer", description: "Error group ID" },
11
+ group_id: { type: "string", description: "Error group ID" },
12
12
  project_id: { type: "integer", description: "Project ID (uses default if not specified)" },
13
13
  mute: { type: "boolean", description: "true to mute, false to unmute (default: true)" }
14
14
  },
@@ -20,9 +20,7 @@ module AirbrakeMcp
20
20
  pid = project_id || client.project_id
21
21
 
22
22
  unless pid
23
- return MCP::Tool::Response.new([
24
- MCP::Content::Text.new("Error: No project_id specified and no default configured")
25
- ], error: true)
23
+ return ResponseHelper.text_response("Error: No project_id specified and no default configured", error: true)
26
24
  end
27
25
 
28
26
  if mute
@@ -33,17 +31,11 @@ module AirbrakeMcp
33
31
  action = "unmuted"
34
32
  end
35
33
 
36
- MCP::Tool::Response.new([
37
- MCP::Content::Text.new("Error group ##{group_id} #{action}")
38
- ])
34
+ ResponseHelper.text_response("Error group ##{group_id} #{action}")
39
35
  rescue Client::NotFoundError
40
- MCP::Tool::Response.new([
41
- MCP::Content::Text.new("Error: Error group #{group_id} not found")
42
- ], error: true)
36
+ ResponseHelper.text_response("Error: Error group #{group_id} not found", error: true)
43
37
  rescue Client::ApiError => e
44
- MCP::Tool::Response.new([
45
- MCP::Content::Text.new("Error: #{e.message}")
46
- ], error: true)
38
+ ResponseHelper.text_response("Error: #{e.message}", error: true)
47
39
  end
48
40
  end
49
41
  end
@@ -8,7 +8,7 @@ module AirbrakeMcp
8
8
 
9
9
  input_schema(
10
10
  properties: {
11
- group_id: { type: "integer", description: "Error group ID" },
11
+ group_id: { type: "string", description: "Error group ID" },
12
12
  project_id: { type: "integer", description: "Project ID (uses default if not specified)" },
13
13
  resolved: { type: "boolean", description: "true to resolve, false to unresolve (default: true)" }
14
14
  },
@@ -20,9 +20,7 @@ module AirbrakeMcp
20
20
  pid = project_id || client.project_id
21
21
 
22
22
  unless pid
23
- return MCP::Tool::Response.new([
24
- MCP::Content::Text.new("Error: No project_id specified and no default configured")
25
- ], error: true)
23
+ return ResponseHelper.text_response("Error: No project_id specified and no default configured", error: true)
26
24
  end
27
25
 
28
26
  if resolved
@@ -33,17 +31,11 @@ module AirbrakeMcp
33
31
  action = "unresolved"
34
32
  end
35
33
 
36
- MCP::Tool::Response.new([
37
- MCP::Content::Text.new("Error group ##{group_id} marked as #{action}")
38
- ])
34
+ ResponseHelper.text_response("Error group ##{group_id} marked as #{action}")
39
35
  rescue Client::NotFoundError
40
- MCP::Tool::Response.new([
41
- MCP::Content::Text.new("Error: Error group #{group_id} not found")
42
- ], error: true)
36
+ ResponseHelper.text_response("Error: Error group #{group_id} not found", error: true)
43
37
  rescue Client::ApiError => e
44
- MCP::Tool::Response.new([
45
- MCP::Content::Text.new("Error: #{e.message}")
46
- ], error: true)
38
+ ResponseHelper.text_response("Error: #{e.message}", error: true)
47
39
  end
48
40
  end
49
41
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AirbrakeMcp
4
- VERSION = '1.0.3'
4
+ VERSION = '1.0.5'
5
5
  end
data/lib/airbrake_mcp.rb CHANGED
@@ -5,6 +5,7 @@ require 'mcp'
5
5
  require_relative 'airbrake_mcp/version'
6
6
  require_relative 'airbrake_mcp/client'
7
7
  require_relative 'airbrake_mcp/formatters'
8
+ require_relative 'airbrake_mcp/response_helper'
8
9
  require_relative 'airbrake_mcp/tools/list_projects'
9
10
  require_relative 'airbrake_mcp/tools/list_errors'
10
11
  require_relative 'airbrake_mcp/tools/get_error'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: airbrake_mcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - httplab
@@ -95,6 +95,7 @@ files:
95
95
  - lib/airbrake_mcp.rb
96
96
  - lib/airbrake_mcp/client.rb
97
97
  - lib/airbrake_mcp/formatters.rb
98
+ - lib/airbrake_mcp/response_helper.rb
98
99
  - lib/airbrake_mcp/tools/get_error.rb
99
100
  - lib/airbrake_mcp/tools/list_errors.rb
100
101
  - lib/airbrake_mcp/tools/list_notices.rb