airbrake_mcp 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 554243b4d4b33ab9844e997247d1969b7fa8eb91bfb3206ad58b5d0ead331cff
4
+ data.tar.gz: ba8ef0c4be17a882003b4f79b795226ba18448bc89dfd0429dc408366d0968a8
5
+ SHA512:
6
+ metadata.gz: 8c0d03b6f73def14080ac2ffc7a71311fc3bdd902b3be4893c217dad80a712d93660dd67590c2ab0dd5558148656eb58e96cdd7cb6072dd79d26dfca57adff21
7
+ data.tar.gz: 99ce5a79ea2ba8101f46a055a9ebdc65fdd2c199865619077f902bd3dad42fdbac4de0a6174dfd988852884e6b4b6cae644cd1601abce2bd4a6766672ce413ef
data/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ ## [1.0.0] - 2025-01-05
4
+
5
+ ### Added
6
+ - Initial release
7
+ - MCP server with STDIO transport
8
+ - Airbrake API client
9
+ - Tools:
10
+ - `list_projects` - List all accessible Airbrake projects
11
+ - `list_errors` - List error groups with filtering options
12
+ - `get_error` - Get detailed error information with backtrace
13
+ - `list_notices` - List individual error occurrences
14
+ - `resolve_error` - Mark errors as resolved/unresolved
15
+ - `mute_error` - Mute/unmute error notifications
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 httplab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # Airbrake MCP Server
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/airbrake_mcp.svg)](https://badge.fury.io/rb/airbrake_mcp)
4
+
5
+ MCP (Model Context Protocol) server for Airbrake error tracking integration with Claude Code.
6
+
7
+ ## Features
8
+
9
+ - List Airbrake projects
10
+ - List and filter error groups
11
+ - Get detailed error information with backtraces
12
+ - List individual error occurrences (notices)
13
+ - Resolve/unresolve errors
14
+ - Mute/unmute error notifications
15
+
16
+ ## Requirements
17
+
18
+ - Ruby 3.0+
19
+ - Airbrake User API Key
20
+
21
+ ## Installation
22
+
23
+ ### Via RubyGems
24
+
25
+ ```bash
26
+ gem install airbrake_mcp
27
+ ```
28
+
29
+ ### From Source
30
+
31
+ ```bash
32
+ git clone https://github.com/httplab/airbrake_mcp.git
33
+ cd airbrake_mcp
34
+ bundle install
35
+ ```
36
+
37
+ ## Configuration
38
+
39
+ ### Getting Your API Key
40
+
41
+ 1. Log in to Airbrake
42
+ 2. Go to Profile Settings
43
+ 3. Find "User API Key" section
44
+ 4. Copy your key
45
+
46
+ ## Claude Code Integration
47
+
48
+ Add to `~/.claude/settings.json` or project's `.claude/settings.local.json`:
49
+
50
+ ```json
51
+ {
52
+ "mcpServers": {
53
+ "airbrake": {
54
+ "command": "airbrake_mcp",
55
+ "env": {
56
+ "AIRBRAKE_USER_KEY": "your_user_key",
57
+ "AIRBRAKE_PROJECT_ID": "123456"
58
+ }
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ Or if installed from source:
65
+
66
+ ```json
67
+ {
68
+ "mcpServers": {
69
+ "airbrake": {
70
+ "command": "/path/to/airbrake_mcp/exe/airbrake_mcp",
71
+ "env": {
72
+ "AIRBRAKE_USER_KEY": "your_user_key",
73
+ "AIRBRAKE_PROJECT_ID": "123456"
74
+ }
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ ## Available Tools
81
+
82
+ ### list_projects
83
+ List all Airbrake projects accessible to the user.
84
+
85
+ ### list_errors
86
+ List error groups with filtering options.
87
+
88
+ Parameters:
89
+ - `project_id` (optional) - Project ID, uses default if not specified
90
+ - `page` (optional) - Page number, default: 1
91
+ - `limit` (optional) - Results per page, default: 20
92
+ - `resolved` (optional) - Filter by resolved status (true/false)
93
+
94
+ ### get_error
95
+ Get detailed information about a specific error group including backtrace.
96
+
97
+ Parameters:
98
+ - `group_id` (required) - Error group ID
99
+ - `project_id` (optional) - Project ID
100
+
101
+ ### list_notices
102
+ List individual occurrences/notices for an error group.
103
+
104
+ Parameters:
105
+ - `group_id` (required) - Error group ID
106
+ - `project_id` (optional) - Project ID
107
+ - `page` (optional) - Page number, default: 1
108
+ - `limit` (optional) - Results per page, default: 10
109
+
110
+ ### resolve_error
111
+ Mark an error group as resolved or unresolve it.
112
+
113
+ Parameters:
114
+ - `group_id` (required) - Error group ID
115
+ - `project_id` (optional) - Project ID
116
+ - `resolved` (optional) - true to resolve, false to unresolve, default: true
117
+
118
+ ### mute_error
119
+ Mute or unmute an error group (suppress/enable notifications).
120
+
121
+ Parameters:
122
+ - `group_id` (required) - Error group ID
123
+ - `project_id` (optional) - Project ID
124
+ - `mute` (optional) - true to mute, false to unmute, default: true
125
+
126
+ ## Development
127
+
128
+ ### Running Tests
129
+
130
+ ```bash
131
+ bundle exec rspec
132
+ ```
133
+
134
+ ### Building the Gem
135
+
136
+ ```bash
137
+ gem build airbrake_mcp.gemspec
138
+ ```
139
+
140
+ ## License
141
+
142
+ MIT
data/exe/airbrake_mcp ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'dotenv/load'
6
+
7
+ require 'airbrake_mcp'
8
+
9
+ # Initialize Airbrake client
10
+ client = AirbrakeMcp::Client.new(
11
+ user_key: ENV.fetch('AIRBRAKE_USER_KEY') { raise 'AIRBRAKE_USER_KEY environment variable is required' },
12
+ project_id: ENV['AIRBRAKE_PROJECT_ID']&.to_i
13
+ )
14
+
15
+ # Create MCP server
16
+ server = MCP::Server.new(
17
+ name: 'airbrake',
18
+ version: AirbrakeMcp::VERSION,
19
+ tools: [
20
+ AirbrakeMcp::Tools::ListProjects,
21
+ AirbrakeMcp::Tools::ListErrors,
22
+ AirbrakeMcp::Tools::GetError,
23
+ AirbrakeMcp::Tools::ListNotices,
24
+ AirbrakeMcp::Tools::ResolveError,
25
+ AirbrakeMcp::Tools::MuteError
26
+ ],
27
+ server_context: { client: client }
28
+ )
29
+
30
+ # Start STDIO transport
31
+ transport = MCP::Server::Transports::StdioTransport.new(server)
32
+ transport.open
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'json'
5
+
6
+ module AirbrakeMcp
7
+ class Client
8
+ BASE_URL = 'https://api.airbrake.io/api/v4'
9
+
10
+ def initialize(user_key:, project_id: nil)
11
+ @user_key = user_key
12
+ @project_id = project_id
13
+ end
14
+
15
+ attr_reader :project_id
16
+
17
+ def projects
18
+ get('/projects')['projects']
19
+ end
20
+
21
+ def groups(project_id: @project_id, page: 1, limit: 20)
22
+ get("/projects/#{project_id}/groups", { page: page, limit: limit })
23
+ end
24
+
25
+ def group(group_id, project_id: @project_id)
26
+ get("/projects/#{project_id}/groups/#{group_id}")
27
+ end
28
+
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 })
31
+ end
32
+
33
+ # Write operations
34
+ def resolve_group(group_id, project_id: @project_id)
35
+ put("/projects/#{project_id}/groups/#{group_id}/resolved")
36
+ end
37
+
38
+ def unresolve_group(group_id, project_id: @project_id)
39
+ put("/projects/#{project_id}/groups/#{group_id}/unresolved")
40
+ end
41
+
42
+ def mute_group(group_id, project_id: @project_id)
43
+ put("/projects/#{project_id}/groups/#{group_id}/muted")
44
+ end
45
+
46
+ def unmute_group(group_id, project_id: @project_id)
47
+ put("/projects/#{project_id}/groups/#{group_id}/unmuted")
48
+ end
49
+
50
+ private
51
+
52
+ def get(path, params = {})
53
+ response = connection.get(path, params.merge(key: @user_key))
54
+ handle_response(response)
55
+ end
56
+
57
+ def put(path)
58
+ response = connection.put("#{path}?key=#{@user_key}")
59
+ response.success?
60
+ end
61
+
62
+ def handle_response(response)
63
+ case response.status
64
+ when 200..299
65
+ JSON.parse(response.body)
66
+ when 401
67
+ raise AuthenticationError, 'Invalid API key'
68
+ when 404
69
+ raise NotFoundError, 'Resource not found'
70
+ when 429
71
+ raise RateLimitError, 'Rate limit exceeded'
72
+ else
73
+ raise ApiError, "API error: #{response.status} - #{response.body}"
74
+ end
75
+ end
76
+
77
+ def connection
78
+ @connection ||= Faraday.new(url: BASE_URL) do |f|
79
+ f.request :url_encoded
80
+ f.adapter Faraday.default_adapter
81
+ end
82
+ end
83
+
84
+ class ApiError < StandardError; end
85
+ class AuthenticationError < ApiError; end
86
+ class NotFoundError < ApiError; end
87
+ class RateLimitError < ApiError; end
88
+ end
89
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AirbrakeMcp
4
+ module Formatters
5
+ module_function
6
+
7
+ def format_projects(projects)
8
+ return "No projects found" if projects.empty?
9
+
10
+ output = "Found #{projects.length} projects:\n\n"
11
+
12
+ projects.each do |project|
13
+ output += " [#{project['id']}] #{project['name']}\n"
14
+ end
15
+
16
+ output
17
+ end
18
+
19
+ def format_groups(result)
20
+ groups = result['groups'] || []
21
+ total = result['count'] || groups.length
22
+
23
+ return "No error groups found" if groups.empty?
24
+
25
+ output = "Found #{total} error groups:\n\n"
26
+
27
+ groups.each do |group|
28
+ status = group['resolved'] ? '[RESOLVED]' : '[OPEN]'
29
+ muted = group['muted'] ? ' (muted)' : ''
30
+ error = group['errors']&.first || {}
31
+
32
+ output += "#{status}#{muted} ##{group['id']}\n"
33
+ output += " Type: #{error['type']}\n"
34
+ output += " Message: #{truncate(error['message'], 100)}\n"
35
+ output += " Count: #{group['noticeCount']} occurrences\n"
36
+ output += " Last seen: #{group['lastNoticeAt']}\n"
37
+ output += " Environment: #{group.dig('context', 'environment') || 'N/A'}\n"
38
+ output += "\n"
39
+ end
40
+
41
+ output
42
+ end
43
+
44
+ def format_group_detail(group)
45
+ error = group['errors']&.first || {}
46
+
47
+ output = "Error Group ##{group['id']}\n"
48
+ output += "=" * 50 + "\n\n"
49
+
50
+ output += "Status: #{group['resolved'] ? 'Resolved' : 'Open'}\n"
51
+ output += "Muted: #{group['muted'] ? 'Yes' : 'No'}\n"
52
+ output += "Type: #{error['type']}\n"
53
+ output += "Message: #{error['message']}\n"
54
+ output += "Total occurrences: #{group['noticeCount']}\n"
55
+ output += "First seen: #{group['createdAt']}\n"
56
+ output += "Last seen: #{group['lastNoticeAt']}\n"
57
+ output += "Environment: #{group.dig('context', 'environment') || 'N/A'}\n"
58
+
59
+ if group['lastDeployAt']
60
+ output += "Last deploy: #{group['lastDeployAt']}\n"
61
+ end
62
+
63
+ output += "\n"
64
+ output += "Backtrace:\n"
65
+ output += "-" * 30 + "\n"
66
+
67
+ backtrace = error['backtrace'] || []
68
+ backtrace.first(15).each_with_index do |frame, i|
69
+ file = frame['file'] || '?'
70
+ line = frame['line'] || '?'
71
+ func = frame['function'] || '?'
72
+ output += " #{i + 1}. #{file}:#{line} in `#{func}`\n"
73
+ end
74
+
75
+ if backtrace.length > 15
76
+ output += " ... and #{backtrace.length - 15} more frames\n"
77
+ end
78
+
79
+ output
80
+ end
81
+
82
+ def format_notices(result)
83
+ notices = result['notices'] || []
84
+
85
+ return "No notices found" if notices.empty?
86
+
87
+ output = "Error occurrences:\n\n"
88
+
89
+ notices.each_with_index do |notice, i|
90
+ error = notice['errors']&.first || {}
91
+
92
+ output += "#{i + 1}. Notice ##{notice['id']}\n"
93
+ output += " Time: #{notice['createdAt']}\n"
94
+ output += " Type: #{error['type']}\n"
95
+ output += " Message: #{truncate(error['message'], 80)}\n"
96
+
97
+ context = notice['context'] || {}
98
+ if context['url']
99
+ output += " URL: #{context['url']}\n"
100
+ end
101
+ if context['userAgent']
102
+ output += " User-Agent: #{truncate(context['userAgent'], 60)}\n"
103
+ end
104
+ if context['userId']
105
+ output += " User ID: #{context['userId']}\n"
106
+ end
107
+
108
+ output += "\n"
109
+ end
110
+
111
+ output
112
+ end
113
+
114
+ def truncate(str, length)
115
+ return '' if str.nil?
116
+ str.length > length ? "#{str[0...length]}..." : str
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AirbrakeMcp
4
+ module Tools
5
+ class GetError < MCP::Tool
6
+ tool_name "get_error"
7
+ description "Get detailed information about a specific error group including backtrace"
8
+
9
+ input_schema(
10
+ properties: {
11
+ group_id: { type: "integer", description: "Error group ID" },
12
+ project_id: { type: "integer", description: "Project ID (uses default if not specified)" }
13
+ },
14
+ required: ["group_id"]
15
+ )
16
+
17
+ def self.call(group_id:, project_id: nil, server_context:)
18
+ client = server_context[:client]
19
+ pid = project_id || client.project_id
20
+
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)
25
+ end
26
+
27
+ group = client.group(group_id, project_id: pid)
28
+
29
+ MCP::Tool::Response.new([
30
+ MCP::Content::Text.new(Formatters.format_group_detail(group))
31
+ ])
32
+ rescue Client::NotFoundError
33
+ MCP::Tool::Response.new([
34
+ MCP::Content::Text.new("Error: Error group #{group_id} not found")
35
+ ], error: true)
36
+ rescue Client::ApiError => e
37
+ MCP::Tool::Response.new([
38
+ MCP::Content::Text.new("Error: #{e.message}")
39
+ ], error: true)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AirbrakeMcp
4
+ module Tools
5
+ class ListErrors < MCP::Tool
6
+ tool_name "list_errors"
7
+ description "List error groups from Airbrake with filtering options"
8
+
9
+ input_schema(
10
+ properties: {
11
+ project_id: { type: "integer", description: "Project ID (uses default if not specified)" },
12
+ page: { type: "integer", description: "Page number (default: 1)" },
13
+ limit: { type: "integer", description: "Results per page (default: 20, max: 100)" },
14
+ resolved: { type: "boolean", description: "Filter by resolved status (true/false)" }
15
+ },
16
+ required: []
17
+ )
18
+
19
+ def self.call(project_id: nil, page: 1, limit: 20, resolved: nil, server_context:)
20
+ client = server_context[:client]
21
+ pid = project_id || client.project_id
22
+
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)
27
+ end
28
+
29
+ result = client.groups(project_id: pid, page: page, limit: limit)
30
+
31
+ # Client-side filtering for resolved status if specified
32
+ if !resolved.nil? && result['groups']
33
+ result['groups'] = result['groups'].select { |g| g['resolved'] == resolved }
34
+ end
35
+
36
+ MCP::Tool::Response.new([
37
+ MCP::Content::Text.new(Formatters.format_groups(result))
38
+ ])
39
+ rescue Client::ApiError => e
40
+ MCP::Tool::Response.new([
41
+ MCP::Content::Text.new("Error: #{e.message}")
42
+ ], error: true)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AirbrakeMcp
4
+ module Tools
5
+ class ListNotices < MCP::Tool
6
+ tool_name "list_notices"
7
+ description "List individual occurrences/notices for an error group"
8
+
9
+ input_schema(
10
+ properties: {
11
+ group_id: { type: "integer", description: "Error group ID" },
12
+ project_id: { type: "integer", description: "Project ID (uses default if not specified)" },
13
+ page: { type: "integer", description: "Page number (default: 1)" },
14
+ limit: { type: "integer", description: "Results per page (default: 10)" }
15
+ },
16
+ required: ["group_id"]
17
+ )
18
+
19
+ def self.call(group_id:, project_id: nil, page: 1, limit: 10, server_context:)
20
+ client = server_context[:client]
21
+ pid = project_id || client.project_id
22
+
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)
27
+ end
28
+
29
+ result = client.notices(group_id, project_id: pid, page: page, limit: limit)
30
+
31
+ MCP::Tool::Response.new([
32
+ MCP::Content::Text.new(Formatters.format_notices(result))
33
+ ])
34
+ rescue Client::NotFoundError
35
+ MCP::Tool::Response.new([
36
+ MCP::Content::Text.new("Error: Error group #{group_id} not found")
37
+ ], error: true)
38
+ rescue Client::ApiError => e
39
+ MCP::Tool::Response.new([
40
+ MCP::Content::Text.new("Error: #{e.message}")
41
+ ], error: true)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AirbrakeMcp
4
+ module Tools
5
+ class ListProjects < MCP::Tool
6
+ tool_name "list_projects"
7
+ description "List all Airbrake projects accessible to the user"
8
+
9
+ def self.call(server_context:)
10
+ client = server_context[:client]
11
+ projects = client.projects
12
+
13
+ MCP::Tool::Response.new([
14
+ MCP::Content::Text.new(Formatters.format_projects(projects))
15
+ ])
16
+ rescue Client::ApiError => e
17
+ MCP::Tool::Response.new([
18
+ MCP::Content::Text.new("Error: #{e.message}")
19
+ ], error: true)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AirbrakeMcp
4
+ module Tools
5
+ class MuteError < MCP::Tool
6
+ tool_name "mute_error"
7
+ description "Mute or unmute an error group (suppress/enable notifications)"
8
+
9
+ input_schema(
10
+ properties: {
11
+ group_id: { type: "integer", description: "Error group ID" },
12
+ project_id: { type: "integer", description: "Project ID (uses default if not specified)" },
13
+ mute: { type: "boolean", description: "true to mute, false to unmute (default: true)" }
14
+ },
15
+ required: ["group_id"]
16
+ )
17
+
18
+ def self.call(group_id:, project_id: nil, mute: true, server_context:)
19
+ client = server_context[:client]
20
+ pid = project_id || client.project_id
21
+
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)
26
+ end
27
+
28
+ if mute
29
+ client.mute_group(group_id, project_id: pid)
30
+ action = "muted"
31
+ else
32
+ client.unmute_group(group_id, project_id: pid)
33
+ action = "unmuted"
34
+ end
35
+
36
+ MCP::Tool::Response.new([
37
+ MCP::Content::Text.new("Error group ##{group_id} #{action}")
38
+ ])
39
+ rescue Client::NotFoundError
40
+ MCP::Tool::Response.new([
41
+ MCP::Content::Text.new("Error: Error group #{group_id} not found")
42
+ ], error: true)
43
+ rescue Client::ApiError => e
44
+ MCP::Tool::Response.new([
45
+ MCP::Content::Text.new("Error: #{e.message}")
46
+ ], error: true)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AirbrakeMcp
4
+ module Tools
5
+ class ResolveError < MCP::Tool
6
+ tool_name "resolve_error"
7
+ description "Mark an error group as resolved or unresolve it"
8
+
9
+ input_schema(
10
+ properties: {
11
+ group_id: { type: "integer", description: "Error group ID" },
12
+ project_id: { type: "integer", description: "Project ID (uses default if not specified)" },
13
+ resolved: { type: "boolean", description: "true to resolve, false to unresolve (default: true)" }
14
+ },
15
+ required: ["group_id"]
16
+ )
17
+
18
+ def self.call(group_id:, project_id: nil, resolved: true, server_context:)
19
+ client = server_context[:client]
20
+ pid = project_id || client.project_id
21
+
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)
26
+ end
27
+
28
+ if resolved
29
+ client.resolve_group(group_id, project_id: pid)
30
+ action = "resolved"
31
+ else
32
+ client.unresolve_group(group_id, project_id: pid)
33
+ action = "unresolved"
34
+ end
35
+
36
+ MCP::Tool::Response.new([
37
+ MCP::Content::Text.new("Error group ##{group_id} marked as #{action}")
38
+ ])
39
+ rescue Client::NotFoundError
40
+ MCP::Tool::Response.new([
41
+ MCP::Content::Text.new("Error: Error group #{group_id} not found")
42
+ ], error: true)
43
+ rescue Client::ApiError => e
44
+ MCP::Tool::Response.new([
45
+ MCP::Content::Text.new("Error: #{e.message}")
46
+ ], error: true)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AirbrakeMcp
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mcp'
4
+
5
+ require_relative 'airbrake_mcp/version'
6
+ require_relative 'airbrake_mcp/client'
7
+ require_relative 'airbrake_mcp/formatters'
8
+ require_relative 'airbrake_mcp/tools/list_projects'
9
+ require_relative 'airbrake_mcp/tools/list_errors'
10
+ require_relative 'airbrake_mcp/tools/get_error'
11
+ require_relative 'airbrake_mcp/tools/list_notices'
12
+ require_relative 'airbrake_mcp/tools/resolve_error'
13
+ require_relative 'airbrake_mcp/tools/mute_error'
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: airbrake_mcp
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - httplab
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-01-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mcp
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dotenv
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: webmock
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ description: Model Context Protocol (MCP) server for Claude Code integration with
84
+ Airbrake error tracking
85
+ email:
86
+ - info@httplab.ru
87
+ executables:
88
+ - airbrake_mcp
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - CHANGELOG.md
93
+ - LICENSE
94
+ - README.md
95
+ - exe/airbrake_mcp
96
+ - lib/airbrake_mcp.rb
97
+ - lib/airbrake_mcp/client.rb
98
+ - lib/airbrake_mcp/formatters.rb
99
+ - lib/airbrake_mcp/tools/get_error.rb
100
+ - lib/airbrake_mcp/tools/list_errors.rb
101
+ - lib/airbrake_mcp/tools/list_notices.rb
102
+ - lib/airbrake_mcp/tools/list_projects.rb
103
+ - lib/airbrake_mcp/tools/mute_error.rb
104
+ - lib/airbrake_mcp/tools/resolve_error.rb
105
+ - lib/airbrake_mcp/version.rb
106
+ homepage: https://github.com/httplab/airbrake_mcp
107
+ licenses:
108
+ - MIT
109
+ metadata:
110
+ homepage_uri: https://github.com/httplab/airbrake_mcp
111
+ source_code_uri: https://github.com/httplab/airbrake_mcp
112
+ changelog_uri: https://github.com/httplab/airbrake_mcp/blob/master/CHANGELOG.md
113
+ post_install_message:
114
+ rdoc_options: []
115
+ require_paths:
116
+ - lib
117
+ required_ruby_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: 3.0.0
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ requirements: []
128
+ rubygems_version: 3.4.10
129
+ signing_key:
130
+ specification_version: 4
131
+ summary: MCP server for Airbrake error tracking
132
+ test_files: []