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 +7 -0
- data/CHANGELOG.md +15 -0
- data/LICENSE +21 -0
- data/README.md +142 -0
- data/exe/airbrake_mcp +32 -0
- data/lib/airbrake_mcp/client.rb +89 -0
- data/lib/airbrake_mcp/formatters.rb +119 -0
- data/lib/airbrake_mcp/tools/get_error.rb +43 -0
- data/lib/airbrake_mcp/tools/list_errors.rb +46 -0
- data/lib/airbrake_mcp/tools/list_notices.rb +45 -0
- data/lib/airbrake_mcp/tools/list_projects.rb +23 -0
- data/lib/airbrake_mcp/tools/mute_error.rb +50 -0
- data/lib/airbrake_mcp/tools/resolve_error.rb +50 -0
- data/lib/airbrake_mcp/version.rb +5 -0
- data/lib/airbrake_mcp.rb +13 -0
- metadata +132 -0
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
|
+
[](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
|
data/lib/airbrake_mcp.rb
ADDED
|
@@ -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: []
|