scout_apm_mcp 0.1.3 → 0.1.4
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 +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +36 -7
- data/lib/scout_apm_mcp/client.rb +53 -28
- data/lib/scout_apm_mcp/helpers.rb +54 -6
- data/lib/scout_apm_mcp/server.rb +181 -52
- data/lib/scout_apm_mcp/version.rb +1 -1
- data/sig/scout_apm_mcp.rbs +0 -1
- metadata +49 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ce6703433e4f11c4cf2769a636135ed7a429707a84a09e196e7151b43cb368b2
|
|
4
|
+
data.tar.gz: 2ddf028b1023a364f4eb5d6afc8f1693549ce4ace041f7d12487719f4513ef3d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1f1ff860d8e236a1338d4f1ceb62a0a6620ff667dc266cc528a7ffd8eceaaf5f4a874612fcc7e8fe45b64a83c446d42af6eca639d13b4ee5cf71b76ce7e7b6b6
|
|
7
|
+
data.tar.gz: 3cadb0d9852c1e661630c69148cfdb2d9c85076094e4672f5b0358448015ade8d13bc800c04a67c1b105ddf66b1ca842ac056c8e38b12b158c3c6290a289daa7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 0.1.4 (2025-12-08)
|
|
4
|
+
|
|
5
|
+
- Enhanced API client and documentation for improved endpoint management
|
|
6
|
+
- Refactored RSpec test suite with improved organization and clarity (split into focused spec files by component)
|
|
7
|
+
- Added RuboCop configuration (replacing Standard) with custom rules for RSpec and strict linting
|
|
8
|
+
- Updated trunk configuration to use RuboCop instead of Standard
|
|
9
|
+
- Added Code of Conduct, Governance, and Security Guidelines documents
|
|
10
|
+
- Updated MCP server configuration to use `gem exec` command with Ruby version specification
|
|
11
|
+
- Updated GitHub Actions workflow dependencies
|
|
12
|
+
- Updated bundler dependencies for improved compatibility
|
|
13
|
+
|
|
3
14
|
## 0.1.3 (2025-11-21)
|
|
4
15
|
|
|
5
16
|
- Custom exception classes (`ScoutApmMcp::Error`, `ScoutApmMcp::AuthError`, `ScoutApmMcp::APIError`) for better error handling
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# scout_apm_mcp
|
|
2
2
|
|
|
3
|
-
[](https://badge.fury.io/rb/scout_apm_mcp) [](https://github.com/amkisko/scout_apm_mcp.rb/actions/workflows/test.yml) [](https://codecov.io/gh/amkisko/scout_apm_mcp.rb)
|
|
4
4
|
|
|
5
5
|
Ruby gem providing ScoutAPM API client and MCP (Model Context Protocol) server tools for fetching traces, endpoints, metrics, errors, and insights. Integrates with MCP-compatible clients like Cursor IDE, Claude Desktop, and other MCP-enabled tools.
|
|
6
6
|
|
|
@@ -20,6 +20,12 @@ Sponsored by [Kisko Labs](https://www.kiskolabs.com).
|
|
|
20
20
|
2. In 1Password create an item with the name "Scout APM API" and store the API key in a new field named API_KEY
|
|
21
21
|
3. Configure your favorite service to use local MCP server, ensure OP_ENV_ENTRY_PATH has correct vault and item names (both are visible in 1Password UI)
|
|
22
22
|
|
|
23
|
+
### Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
gem install scout_apm_mcp
|
|
27
|
+
```
|
|
28
|
+
|
|
23
29
|
### Cursor IDE Configuration
|
|
24
30
|
|
|
25
31
|
For Cursor IDE, create or update `.cursor/mcp.json` in your project:
|
|
@@ -28,15 +34,19 @@ For Cursor IDE, create or update `.cursor/mcp.json` in your project:
|
|
|
28
34
|
{
|
|
29
35
|
"mcpServers": {
|
|
30
36
|
"scout-apm": {
|
|
31
|
-
"command": "
|
|
37
|
+
"command": "gem",
|
|
38
|
+
"args": ["exec", "scout_apm_mcp"],
|
|
32
39
|
"env": {
|
|
33
|
-
"OP_ENV_ENTRY_PATH": "op://Vault Name/Item Name"
|
|
40
|
+
"OP_ENV_ENTRY_PATH": "op://Vault Name/Item Name",
|
|
41
|
+
"RUBY_VERSION": "3.4.7"
|
|
34
42
|
}
|
|
35
43
|
}
|
|
36
44
|
}
|
|
37
45
|
}
|
|
38
46
|
```
|
|
39
47
|
|
|
48
|
+
**Note**: Using `gem exec` ensures the correct Ruby version is used. If you're using a Ruby version manager like [mise](https://mise.jdx.dev/) or [rbenv](https://github.com/rbenv/rbenv), set the `RUBY_VERSION` environment variable to match your desired Ruby version. The `gem exec` command will automatically use the correct Ruby version based on your version manager configuration.
|
|
49
|
+
|
|
40
50
|
### Claude Desktop Configuration
|
|
41
51
|
|
|
42
52
|
For Claude Desktop, edit the MCP configuration file:
|
|
@@ -48,16 +58,18 @@ For Claude Desktop, edit the MCP configuration file:
|
|
|
48
58
|
{
|
|
49
59
|
"mcpServers": {
|
|
50
60
|
"scout-apm": {
|
|
51
|
-
"command": "
|
|
61
|
+
"command": "gem",
|
|
62
|
+
"args": ["exec", "scout_apm_mcp"],
|
|
52
63
|
"env": {
|
|
53
|
-
"OP_ENV_ENTRY_PATH": "op://Vault Name/Item Name"
|
|
64
|
+
"OP_ENV_ENTRY_PATH": "op://Vault Name/Item Name",
|
|
65
|
+
"RUBY_VERSION": "3.4.7"
|
|
54
66
|
}
|
|
55
67
|
}
|
|
56
68
|
}
|
|
57
69
|
}
|
|
58
70
|
```
|
|
59
71
|
|
|
60
|
-
**Note**: After updating the configuration, restart Claude Desktop for changes to take effect.
|
|
72
|
+
**Note**: After updating the configuration, restart Claude Desktop for changes to take effect. Using `gem exec` ensures the correct Ruby version is used. If you're using a Ruby version manager like [mise](https://mise.jdx.dev/) or [rbenv](https://github.com/rbenv/rbenv), set the `RUBY_VERSION` environment variable to match your desired Ruby version. The `gem exec` command will automatically use the correct Ruby version based on your version manager configuration.
|
|
61
73
|
|
|
62
74
|
### Security Best Practice
|
|
63
75
|
|
|
@@ -107,6 +119,22 @@ scout_apm_mcp
|
|
|
107
119
|
|
|
108
120
|
The server will start and communicate via STDIN/STDOUT using the MCP protocol. Make sure you have your ScoutAPM API key configured (see API Key Management section below).
|
|
109
121
|
|
|
122
|
+
## Upgrading
|
|
123
|
+
|
|
124
|
+
To upgrade to the latest version of the gem:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
gem update scout_apm_mcp
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
If you're using Bundler, update your `Gemfile.lock`:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
bundle update scout_apm_mcp
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Note**: As of version 0.1.3, client methods return extracted data (arrays, hashes) instead of full API response structures. This is a breaking change from previous versions. See the [CHANGELOG.md](CHANGELOG.md) for details on breaking changes and new features.
|
|
137
|
+
|
|
110
138
|
## Features
|
|
111
139
|
|
|
112
140
|
- **ScoutAPM API Client**: Full-featured client for ScoutAPM REST API
|
|
@@ -233,10 +261,11 @@ api_key = ScoutApmMcp::Helpers.get_api_key(
|
|
|
233
261
|
### Endpoints
|
|
234
262
|
|
|
235
263
|
- `list_endpoints(app_id, from:, to:)` - List all endpoints
|
|
236
|
-
- `get_endpoint(app_id, endpoint_id)` - Get endpoint details
|
|
237
264
|
- `get_endpoint_metrics(app_id, endpoint_id, metric_type, from:, to:)` - Get endpoint metrics
|
|
238
265
|
- `list_endpoint_traces(app_id, endpoint_id, from:, to:)` - List endpoint traces
|
|
239
266
|
|
|
267
|
+
Note: The API doesn't provide a direct endpoint detail endpoint. Use `list_endpoints` and filter by endpoint_id to get specific endpoint information.
|
|
268
|
+
|
|
240
269
|
### Traces
|
|
241
270
|
|
|
242
271
|
- `fetch_trace(app_id, trace_id)` - Fetch detailed trace information
|
data/lib/scout_apm_mcp/client.rb
CHANGED
|
@@ -27,8 +27,12 @@ module ScoutApmMcp
|
|
|
27
27
|
|
|
28
28
|
# @param api_key [String] ScoutAPM API key
|
|
29
29
|
# @param api_base [String] API base URL (default: https://scoutapm.com/api/v0)
|
|
30
|
+
# @raise [ArgumentError] if api_key is nil or empty
|
|
30
31
|
def initialize(api_key:, api_base: API_BASE)
|
|
31
|
-
|
|
32
|
+
if api_key.nil? || api_key.to_s.strip.empty?
|
|
33
|
+
raise ArgumentError, "API key is required and cannot be nil or empty"
|
|
34
|
+
end
|
|
35
|
+
@api_key = api_key.to_s
|
|
32
36
|
@api_base = api_base
|
|
33
37
|
@user_agent = "scout-apm-mcp-rb/#{VERSION}"
|
|
34
38
|
end
|
|
@@ -42,7 +46,6 @@ module ScoutApmMcp
|
|
|
42
46
|
response = make_request(uri)
|
|
43
47
|
apps = response.dig("results", "apps") || []
|
|
44
48
|
|
|
45
|
-
# Filter by active_since if provided
|
|
46
49
|
if active_since
|
|
47
50
|
active_time = Helpers.parse_time(active_since)
|
|
48
51
|
apps = apps.select do |app|
|
|
@@ -84,8 +87,15 @@ module ScoutApmMcp
|
|
|
84
87
|
# @param metric_type [String] Metric type (apdex, response_time, response_time_95th, errors, throughput, queue_time)
|
|
85
88
|
# @param from [String, nil] Start time in ISO 8601 format
|
|
86
89
|
# @param to [String, nil] End time in ISO 8601 format
|
|
90
|
+
# @param range [String, nil] Quick time range (e.g., "30min", "1day", "3days", "7days"). If provided, calculates from/to automatically.
|
|
87
91
|
# @return [Hash] Hash containing metric series data
|
|
88
|
-
def get_metric(app_id, metric_type, from: nil, to: nil)
|
|
92
|
+
def get_metric(app_id, metric_type, from: nil, to: nil, range: nil)
|
|
93
|
+
if range
|
|
94
|
+
calculated = Helpers.calculate_range(range: range, to: to)
|
|
95
|
+
from = calculated[:from]
|
|
96
|
+
to = calculated[:to]
|
|
97
|
+
end
|
|
98
|
+
|
|
89
99
|
validate_metric_params(metric_type, from, to)
|
|
90
100
|
uri = URI("#{@api_base}/apps/#{app_id}/metrics/#{metric_type}")
|
|
91
101
|
uri.query = build_query_string(from: from, to: to)
|
|
@@ -98,8 +108,27 @@ module ScoutApmMcp
|
|
|
98
108
|
# @param app_id [Integer] ScoutAPM application ID
|
|
99
109
|
# @param from [String, nil] Start time in ISO 8601 format
|
|
100
110
|
# @param to [String, nil] End time in ISO 8601 format
|
|
111
|
+
# @param range [String, nil] Quick time range (e.g., "30min", "1day", "3days", "7days"). If provided, calculates from/to automatically.
|
|
101
112
|
# @return [Array<Hash>] Array of endpoint hashes
|
|
102
|
-
def list_endpoints(app_id, from: nil, to: nil)
|
|
113
|
+
def list_endpoints(app_id, from: nil, to: nil, range: nil)
|
|
114
|
+
if from.nil? && to.nil? && range.nil?
|
|
115
|
+
range = "7days"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
if range
|
|
119
|
+
calculated = Helpers.calculate_range(range: range, to: to)
|
|
120
|
+
from = calculated[:from]
|
|
121
|
+
to = calculated[:to]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
now = Time.now.utc
|
|
125
|
+
if from.nil? && to
|
|
126
|
+
calculated = Helpers.calculate_range(range: "7days", to: to)
|
|
127
|
+
from = calculated[:from]
|
|
128
|
+
elsif from && to.nil?
|
|
129
|
+
to = Helpers.format_time(now)
|
|
130
|
+
end
|
|
131
|
+
|
|
103
132
|
validate_time_range(from, to) if from && to
|
|
104
133
|
uri = URI("#{@api_base}/apps/#{app_id}/endpoints")
|
|
105
134
|
uri.query = build_query_string(from: from, to: to)
|
|
@@ -107,18 +136,6 @@ module ScoutApmMcp
|
|
|
107
136
|
response["results"] || []
|
|
108
137
|
end
|
|
109
138
|
|
|
110
|
-
# Get endpoint details
|
|
111
|
-
#
|
|
112
|
-
# @param app_id [Integer] ScoutAPM application ID
|
|
113
|
-
# @param endpoint_id [String] Endpoint ID (base64 URL-encoded)
|
|
114
|
-
# @return [Hash] Endpoint details hash
|
|
115
|
-
def get_endpoint(app_id, endpoint_id)
|
|
116
|
-
encoded_endpoint_id = URI.encode_www_form_component(endpoint_id)
|
|
117
|
-
uri = URI("#{@api_base}/apps/#{app_id}/endpoints/#{encoded_endpoint_id}")
|
|
118
|
-
response = make_request(uri)
|
|
119
|
-
response.dig("results", "endpoint") || response["results"] || {}
|
|
120
|
-
end
|
|
121
|
-
|
|
122
139
|
# Get metric data for a specific endpoint
|
|
123
140
|
#
|
|
124
141
|
# @param app_id [Integer] ScoutAPM application ID
|
|
@@ -126,11 +143,18 @@ module ScoutApmMcp
|
|
|
126
143
|
# @param metric_type [String] Metric type (apdex, response_time, response_time_95th, errors, throughput, queue_time)
|
|
127
144
|
# @param from [String, nil] Start time in ISO 8601 format
|
|
128
145
|
# @param to [String, nil] End time in ISO 8601 format
|
|
146
|
+
# @param range [String, nil] Quick time range (e.g., "30min", "1day", "3days", "7days"). If provided, calculates from/to automatically.
|
|
129
147
|
# @return [Array] Array of metric data points for the specified metric type
|
|
130
|
-
def get_endpoint_metrics(app_id, endpoint_id, metric_type, from: nil, to: nil)
|
|
148
|
+
def get_endpoint_metrics(app_id, endpoint_id, metric_type, from: nil, to: nil, range: nil)
|
|
149
|
+
if range
|
|
150
|
+
calculated = Helpers.calculate_range(range: range, to: to)
|
|
151
|
+
from = calculated[:from]
|
|
152
|
+
to = calculated[:to]
|
|
153
|
+
end
|
|
154
|
+
|
|
131
155
|
validate_metric_params(metric_type, from, to)
|
|
132
|
-
|
|
133
|
-
uri =
|
|
156
|
+
uri = URI(@api_base)
|
|
157
|
+
uri.path = File.join(uri.path, "apps", app_id.to_s, "endpoints", endpoint_id, "metrics", metric_type)
|
|
134
158
|
uri.query = build_query_string(from: from, to: to)
|
|
135
159
|
response = make_request(uri)
|
|
136
160
|
series = response.dig("results", "series") || {}
|
|
@@ -143,19 +167,25 @@ module ScoutApmMcp
|
|
|
143
167
|
# @param endpoint_id [String] Endpoint ID (base64 URL-encoded)
|
|
144
168
|
# @param from [String, nil] Start time in ISO 8601 format
|
|
145
169
|
# @param to [String, nil] End time in ISO 8601 format
|
|
170
|
+
# @param range [String, nil] Quick time range (e.g., "30min", "1day", "3days", "7days"). If provided, calculates from/to automatically.
|
|
146
171
|
# @return [Array<Hash>] Array of trace hashes
|
|
147
|
-
def list_endpoint_traces(app_id, endpoint_id, from: nil, to: nil)
|
|
172
|
+
def list_endpoint_traces(app_id, endpoint_id, from: nil, to: nil, range: nil)
|
|
173
|
+
if range
|
|
174
|
+
calculated = Helpers.calculate_range(range: range, to: to)
|
|
175
|
+
from = calculated[:from]
|
|
176
|
+
to = calculated[:to]
|
|
177
|
+
end
|
|
178
|
+
|
|
148
179
|
validate_time_range(from, to) if from && to
|
|
149
180
|
if from && to
|
|
150
|
-
# Validate that from_time is not older than 7 days
|
|
151
181
|
from_time = Helpers.parse_time(from)
|
|
152
182
|
seven_days_ago = Time.now.utc - (7 * 24 * 60 * 60)
|
|
153
183
|
if from_time < seven_days_ago
|
|
154
184
|
raise ArgumentError, "from_time cannot be older than 7 days"
|
|
155
185
|
end
|
|
156
186
|
end
|
|
157
|
-
|
|
158
|
-
uri =
|
|
187
|
+
uri = URI(@api_base)
|
|
188
|
+
uri.path = File.join(uri.path, "apps", app_id.to_s, "endpoints", endpoint_id, "traces")
|
|
159
189
|
uri.query = build_query_string(from: from, to: to)
|
|
160
190
|
response = make_request(uri)
|
|
161
191
|
response.dig("results", "traces") || []
|
|
@@ -370,7 +400,6 @@ module ScoutApmMcp
|
|
|
370
400
|
response = http.request(request)
|
|
371
401
|
response_data = handle_response_errors(response)
|
|
372
402
|
|
|
373
|
-
# Check for API-level errors in response body
|
|
374
403
|
if response_data.is_a?(Hash)
|
|
375
404
|
header = response_data["header"]
|
|
376
405
|
if header && header["status"]
|
|
@@ -391,21 +420,17 @@ module ScoutApmMcp
|
|
|
391
420
|
raise Error, "Request failed: #{e.class} - #{e.message}"
|
|
392
421
|
end
|
|
393
422
|
|
|
394
|
-
# Handle common response errors and parse JSON
|
|
395
|
-
#
|
|
396
423
|
# @param response [Net::HTTPResponse] HTTP response object
|
|
397
424
|
# @return [Hash, Array] Parsed JSON response
|
|
398
425
|
# @raise [AuthError] When authentication fails
|
|
399
426
|
# @raise [APIError] When the API returns an error response
|
|
400
427
|
def handle_response_errors(response)
|
|
401
|
-
# Try to parse JSON response
|
|
402
428
|
begin
|
|
403
429
|
data = JSON.parse(response.body)
|
|
404
430
|
rescue JSON::ParserError
|
|
405
431
|
raise APIError.new("Invalid JSON response: #{response.body}", status_code: response.code.to_i)
|
|
406
432
|
end
|
|
407
433
|
|
|
408
|
-
# Check for HTTP-level errors
|
|
409
434
|
case response
|
|
410
435
|
when Net::HTTPSuccess
|
|
411
436
|
data
|
|
@@ -14,14 +14,10 @@ module ScoutApmMcp
|
|
|
14
14
|
# @return [String] API key
|
|
15
15
|
# @raise [RuntimeError] if API key cannot be found
|
|
16
16
|
def self.get_api_key(api_key: nil, op_vault: nil, op_item: nil, op_field: "API_KEY")
|
|
17
|
-
# Use provided API key if available
|
|
18
17
|
return api_key if api_key && !api_key.empty?
|
|
19
18
|
|
|
20
|
-
# Check environment variable (may have been set by opdotenv loaded early in server startup)
|
|
21
19
|
api_key = ENV["API_KEY"] || ENV["SCOUT_APM_API_KEY"]
|
|
22
20
|
return api_key if api_key && !api_key.empty?
|
|
23
|
-
|
|
24
|
-
# Try direct 1Password CLI as fallback (opdotenv was already tried in server startup)
|
|
25
21
|
op_env_entry_path = ENV["OP_ENV_ENTRY_PATH"]
|
|
26
22
|
if op_env_entry_path && !op_env_entry_path.empty?
|
|
27
23
|
begin
|
|
@@ -55,7 +51,6 @@ module ScoutApmMcp
|
|
|
55
51
|
api_key = `op read "op://#{op_vault}/#{op_item}/#{op_field}" 2>/dev/null`.strip
|
|
56
52
|
return api_key if api_key && !api_key.empty?
|
|
57
53
|
rescue
|
|
58
|
-
# Silently fail
|
|
59
54
|
end
|
|
60
55
|
end
|
|
61
56
|
|
|
@@ -135,7 +130,6 @@ module ScoutApmMcp
|
|
|
135
130
|
# @return [String] Decoded endpoint ID
|
|
136
131
|
def self.decode_endpoint_id(endpoint_id)
|
|
137
132
|
decoded = Base64.urlsafe_decode64(endpoint_id)
|
|
138
|
-
# Check if decoded result is valid UTF-8
|
|
139
133
|
if decoded.force_encoding(Encoding::UTF_8).valid_encoding?
|
|
140
134
|
decoded.force_encoding(Encoding::UTF_8)
|
|
141
135
|
else
|
|
@@ -200,5 +194,59 @@ module ScoutApmMcp
|
|
|
200
194
|
end: parse_time(to_str)
|
|
201
195
|
}
|
|
202
196
|
end
|
|
197
|
+
|
|
198
|
+
# Parse a time range string into seconds
|
|
199
|
+
#
|
|
200
|
+
# Supports formats like: "30min", "60min", "3hrs", "6hrs", "12hrs", "1day", "3days", "7days"
|
|
201
|
+
# Case-insensitive, supports singular and plural forms
|
|
202
|
+
#
|
|
203
|
+
# @param range_str [String] Time range string (e.g., "30min", "1day", "7days")
|
|
204
|
+
# @return [Integer] Duration in seconds
|
|
205
|
+
# @raise [ArgumentError] If the range string format is invalid
|
|
206
|
+
def self.parse_range(range_str)
|
|
207
|
+
return nil if range_str.nil? || range_str.empty?
|
|
208
|
+
|
|
209
|
+
# Normalize: lowercase, remove spaces, handle singular/plural
|
|
210
|
+
normalized = range_str.downcase.strip.gsub(/\s+/, "")
|
|
211
|
+
|
|
212
|
+
# Match pattern: number followed by unit
|
|
213
|
+
match = normalized.match(/\A(\d+)(min|mins?|hr|hrs?|hour|hours|day|days)\z/)
|
|
214
|
+
unless match
|
|
215
|
+
valid_ranges = %w[30min 60min 3hrs 6hrs 12hrs 1day 3days 7days]
|
|
216
|
+
raise ArgumentError, "Invalid range format: #{range_str}. Valid formats: #{valid_ranges.join(", ")}"
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
value = match[1].to_i
|
|
220
|
+
unit = match[2]
|
|
221
|
+
|
|
222
|
+
case unit
|
|
223
|
+
when /^min/
|
|
224
|
+
value * 60
|
|
225
|
+
when /^hr/, /^hour/
|
|
226
|
+
value * 60 * 60
|
|
227
|
+
when /^day/
|
|
228
|
+
value * 24 * 60 * 60
|
|
229
|
+
else
|
|
230
|
+
raise ArgumentError, "Unknown time unit: #{unit}"
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Calculate from/to times based on a range string
|
|
235
|
+
#
|
|
236
|
+
# @param range [String, nil] Time range string (e.g., "30min", "1day", "3days")
|
|
237
|
+
# @param to [String, nil] End time in ISO 8601 format (defaults to now if not provided)
|
|
238
|
+
# @return [Hash] Hash with :from and :to as ISO 8601 strings
|
|
239
|
+
def self.calculate_range(range:, to: nil)
|
|
240
|
+
return {from: nil, to: to} if range.nil? || range.empty?
|
|
241
|
+
|
|
242
|
+
end_time = to ? parse_time(to) : Time.now.utc
|
|
243
|
+
duration_seconds = parse_range(range)
|
|
244
|
+
start_time = end_time - duration_seconds
|
|
245
|
+
|
|
246
|
+
{
|
|
247
|
+
from: format_time(start_time),
|
|
248
|
+
to: format_time(end_time)
|
|
249
|
+
}
|
|
250
|
+
end
|
|
203
251
|
end
|
|
204
252
|
end
|
data/lib/scout_apm_mcp/server.rb
CHANGED
|
@@ -133,7 +133,6 @@ module ScoutApmMcp
|
|
|
133
133
|
server.register_tool(ListMetricsTool)
|
|
134
134
|
server.register_tool(GetMetricTool)
|
|
135
135
|
server.register_tool(ListEndpointsTool)
|
|
136
|
-
server.register_tool(FetchEndpointTool)
|
|
137
136
|
server.register_tool(GetEndpointMetricsTool)
|
|
138
137
|
server.register_tool(ListEndpointTracesTool)
|
|
139
138
|
server.register_tool(FetchTraceTool)
|
|
@@ -166,10 +165,23 @@ module ScoutApmMcp
|
|
|
166
165
|
|
|
167
166
|
# Applications Tools
|
|
168
167
|
class ListAppsTool < BaseTool
|
|
169
|
-
description
|
|
168
|
+
description <<~DESC
|
|
169
|
+
List all applications accessible with the provided API key.
|
|
170
|
+
|
|
171
|
+
Returns an array of applications with details like name, ID, and last reported time.
|
|
172
|
+
Use the app_id from the results to make subsequent API calls.
|
|
173
|
+
|
|
174
|
+
Optional filtering:
|
|
175
|
+
- active_since: Only return apps that have reported data since this time (ISO 8601 format)
|
|
176
|
+
- Default behavior: Returns all apps (no filtering by default, but API may filter to last 30 days)
|
|
177
|
+
|
|
178
|
+
Example:
|
|
179
|
+
- List all apps: call without parameters
|
|
180
|
+
- List apps active in last 7 days: provide active_since="2025-01-08T00:00:00Z"
|
|
181
|
+
DESC
|
|
170
182
|
|
|
171
183
|
arguments do
|
|
172
|
-
optional(:active_since).maybe(:string).description("ISO 8601 datetime string to filter apps active since that time")
|
|
184
|
+
optional(:active_since).maybe(:string).description("ISO 8601 datetime string to filter apps active since that time (e.g., 2025-01-08T00:00:00Z)")
|
|
173
185
|
end
|
|
174
186
|
|
|
175
187
|
def call(active_since: nil)
|
|
@@ -203,80 +215,151 @@ module ScoutApmMcp
|
|
|
203
215
|
end
|
|
204
216
|
|
|
205
217
|
class GetMetricTool < BaseTool
|
|
206
|
-
description
|
|
218
|
+
description <<~DESC
|
|
219
|
+
Get time-series data for a specific metric type.
|
|
220
|
+
|
|
221
|
+
Available metric types:
|
|
222
|
+
- apdex: Application Performance Index (0-1, higher is better)
|
|
223
|
+
- response_time: Average response time in milliseconds
|
|
224
|
+
- response_time_95th: 95th percentile response time in milliseconds
|
|
225
|
+
- errors: Number of errors
|
|
226
|
+
- throughput: Requests per second
|
|
227
|
+
- queue_time: Time spent in queue in milliseconds
|
|
228
|
+
|
|
229
|
+
You can specify time ranges using:
|
|
230
|
+
1. Quick range templates: range="30min", "1day", "3days", "7days", etc.
|
|
231
|
+
2. Explicit times: from and to with ISO 8601 timestamps
|
|
232
|
+
|
|
233
|
+
Examples:
|
|
234
|
+
- Get response time for last hour: metric_type="response_time", range="1hr"
|
|
235
|
+
- Get error count for last day: metric_type="errors", range="1day"
|
|
236
|
+
- Get metrics for specific range: metric_type="apdex", from="2025-01-15T10:00:00Z", to="2025-01-15T12:00:00Z"
|
|
237
|
+
DESC
|
|
207
238
|
|
|
208
239
|
arguments do
|
|
209
240
|
required(:app_id).filled(:integer).description("ScoutAPM application ID")
|
|
210
241
|
required(:metric_type).filled(:string).description("Metric type: apdex, response_time, response_time_95th, errors, throughput, queue_time")
|
|
211
|
-
optional(:
|
|
212
|
-
optional(:
|
|
242
|
+
optional(:range).maybe(:string).description("Quick time range template: 30min, 60min, 3hrs, 6hrs, 12hrs, 1day, 3days, 7days. If provided, calculates from/to automatically.")
|
|
243
|
+
optional(:from).maybe(:string).description("Start time in ISO 8601 format (e.g., 2025-11-17T15:25:35Z). Ignored if range is provided.")
|
|
244
|
+
optional(:to).maybe(:string).description("End time in ISO 8601 format (e.g., 2025-11-18T15:25:35Z). Used as end point for range if range is provided.")
|
|
213
245
|
end
|
|
214
246
|
|
|
215
|
-
def call(app_id:, metric_type:, from: nil, to: nil)
|
|
216
|
-
get_client.get_metric(app_id, metric_type, from: from, to: to)
|
|
247
|
+
def call(app_id:, metric_type:, range: nil, from: nil, to: nil)
|
|
248
|
+
get_client.get_metric(app_id, metric_type, from: from, to: to, range: range)
|
|
217
249
|
end
|
|
218
250
|
end
|
|
219
251
|
|
|
220
252
|
# Endpoints Tools
|
|
221
253
|
class ListEndpointsTool < BaseTool
|
|
222
|
-
description
|
|
254
|
+
description <<~DESC
|
|
255
|
+
List all endpoints for an application.
|
|
256
|
+
|
|
257
|
+
The API requires timeframe parameters. You can specify time ranges in two ways:
|
|
258
|
+
1. Quick range templates: Use the 'range' parameter (e.g., "30min", "1day", "3days", "7days")
|
|
259
|
+
2. Explicit times: Use 'from' and 'to' parameters with ISO 8601 timestamps
|
|
260
|
+
|
|
261
|
+
If neither from/to nor range are provided, defaults to the last 7 days.
|
|
262
|
+
|
|
263
|
+
Quick range templates (case-insensitive):
|
|
264
|
+
- "30min" or "30mins" - Last 30 minutes
|
|
265
|
+
- "60min" or "60mins" or "1hr" or "1hour" - Last 60 minutes
|
|
266
|
+
- "3hrs" or "3hours" - Last 3 hours
|
|
267
|
+
- "6hrs" or "6hours" - Last 6 hours
|
|
268
|
+
- "12hrs" or "12hours" - Last 12 hours
|
|
269
|
+
- "1day" or "1days" - Last 24 hours
|
|
270
|
+
- "3days" - Last 3 days
|
|
271
|
+
- "7days" - Last 7 days (default)
|
|
272
|
+
|
|
273
|
+
Examples:
|
|
274
|
+
- List endpoints for last 30 minutes: range="30min"
|
|
275
|
+
- List endpoints for last day: range="1day"
|
|
276
|
+
- List endpoints for a specific range: from="2025-01-15T10:00:00Z", to="2025-01-15T12:00:00Z"
|
|
277
|
+
- List endpoints from a specific time to now: from="2025-01-15T10:00:00Z"
|
|
278
|
+
- List endpoints for 1 day ending at specific time: range="1day", to="2025-01-15T12:00:00Z"
|
|
279
|
+
DESC
|
|
223
280
|
|
|
224
281
|
arguments do
|
|
225
282
|
required(:app_id).filled(:integer).description("ScoutAPM application ID")
|
|
226
|
-
optional(:
|
|
227
|
-
optional(:
|
|
283
|
+
optional(:range).maybe(:string).description("Quick time range template: 30min, 60min, 3hrs, 6hrs, 12hrs, 1day, 3days, 7days. If provided, calculates from/to automatically.")
|
|
284
|
+
optional(:from).maybe(:string).description("Start time in ISO 8601 format (e.g., 2025-11-17T15:25:35Z). Ignored if range is provided.")
|
|
285
|
+
optional(:to).maybe(:string).description("End time in ISO 8601 format (e.g., 2025-11-18T15:25:35Z). Used as end point for range if range is provided, otherwise defaults to now.")
|
|
228
286
|
end
|
|
229
287
|
|
|
230
|
-
def call(app_id:, from: nil, to: nil)
|
|
231
|
-
get_client.list_endpoints(app_id, from: from, to: to)
|
|
288
|
+
def call(app_id:, range: nil, from: nil, to: nil)
|
|
289
|
+
get_client.list_endpoints(app_id, from: from, to: to, range: range)
|
|
232
290
|
end
|
|
233
291
|
end
|
|
234
292
|
|
|
235
|
-
class
|
|
236
|
-
description
|
|
293
|
+
class GetEndpointMetricsTool < BaseTool
|
|
294
|
+
description <<~DESC
|
|
295
|
+
Get metric data for a specific endpoint.
|
|
237
296
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
297
|
+
Available metric types:
|
|
298
|
+
- apdex: Application Performance Index (0-1, higher is better)
|
|
299
|
+
- response_time: Average response time in milliseconds
|
|
300
|
+
- response_time_95th: 95th percentile response time in milliseconds
|
|
301
|
+
- errors: Number of errors
|
|
302
|
+
- throughput: Requests per second
|
|
303
|
+
- queue_time: Time spent in queue in milliseconds
|
|
242
304
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
endpoint: client.get_endpoint(app_id, endpoint_id),
|
|
247
|
-
decoded_endpoint: Helpers.decode_endpoint_id(endpoint_id)
|
|
248
|
-
}
|
|
249
|
-
end
|
|
250
|
-
end
|
|
305
|
+
You can specify time ranges using:
|
|
306
|
+
1. Quick range templates: range="30min", "1day", "3days", "7days", etc.
|
|
307
|
+
2. Explicit times: from and to with ISO 8601 timestamps
|
|
251
308
|
|
|
252
|
-
|
|
253
|
-
|
|
309
|
+
Returns time-series data points for the specified metric type.
|
|
310
|
+
|
|
311
|
+
Examples:
|
|
312
|
+
- Get response time for last hour: metric_type="response_time", range="1hr"
|
|
313
|
+
- Get error count for last day: metric_type="errors", range="1day"
|
|
314
|
+
- Get metrics for specific range: metric_type="apdex", from="2025-01-15T10:00:00Z", to="2025-01-15T12:00:00Z"
|
|
315
|
+
DESC
|
|
254
316
|
|
|
255
317
|
arguments do
|
|
256
318
|
required(:app_id).filled(:integer).description("ScoutAPM application ID")
|
|
257
|
-
required(:endpoint_id).filled(:string).description("Endpoint ID (base64 URL-encoded)")
|
|
319
|
+
required(:endpoint_id).filled(:string).description("Endpoint ID (base64 URL-encoded). Extract from ScoutAPM URLs or use ParseScoutURLTool.")
|
|
258
320
|
required(:metric_type).filled(:string).description("Metric type: apdex, response_time, response_time_95th, errors, throughput, queue_time")
|
|
259
|
-
optional(:
|
|
260
|
-
optional(:
|
|
321
|
+
optional(:range).maybe(:string).description("Quick time range template: 30min, 60min, 3hrs, 6hrs, 12hrs, 1day, 3days, 7days. If provided, calculates from/to automatically.")
|
|
322
|
+
optional(:from).maybe(:string).description("Start time in ISO 8601 format (e.g., 2025-11-17T15:25:35Z). Ignored if range is provided.")
|
|
323
|
+
optional(:to).maybe(:string).description("End time in ISO 8601 format (e.g., 2025-11-18T15:25:35Z). Used as end point for range if range is provided.")
|
|
261
324
|
end
|
|
262
325
|
|
|
263
|
-
def call(app_id:, endpoint_id:, metric_type:, from: nil, to: nil)
|
|
264
|
-
get_client.get_endpoint_metrics(app_id, endpoint_id, metric_type, from: from, to: to)
|
|
326
|
+
def call(app_id:, endpoint_id:, metric_type:, range: nil, from: nil, to: nil)
|
|
327
|
+
get_client.get_endpoint_metrics(app_id, endpoint_id, metric_type, from: from, to: to, range: range)
|
|
265
328
|
end
|
|
266
329
|
end
|
|
267
330
|
|
|
268
331
|
class ListEndpointTracesTool < BaseTool
|
|
269
|
-
description
|
|
332
|
+
description <<~DESC
|
|
333
|
+
List traces for a specific endpoint (max 100, within 7 days).
|
|
334
|
+
|
|
335
|
+
Traces are individual request executions that can be analyzed for performance issues.
|
|
336
|
+
Returns up to 100 traces for the specified endpoint within the last 7 days.
|
|
337
|
+
|
|
338
|
+
You can specify time ranges using:
|
|
339
|
+
1. Quick range templates: range="30min", "1day", "3days", "7days", etc.
|
|
340
|
+
2. Explicit times: from and to with ISO 8601 timestamps
|
|
341
|
+
|
|
342
|
+
Time range constraints:
|
|
343
|
+
- If from is provided, it must be within the last 7 days
|
|
344
|
+
- Maximum 100 traces returned per request
|
|
345
|
+
- Use the trace_id from results with FetchTraceTool for detailed analysis
|
|
346
|
+
|
|
347
|
+
Examples:
|
|
348
|
+
- List traces from last hour: range="1hr"
|
|
349
|
+
- List traces from last day: range="1day"
|
|
350
|
+
- List traces for specific range: from="2025-01-15T10:00:00Z", to="2025-01-15T12:00:00Z"
|
|
351
|
+
DESC
|
|
270
352
|
|
|
271
353
|
arguments do
|
|
272
354
|
required(:app_id).filled(:integer).description("ScoutAPM application ID")
|
|
273
|
-
required(:endpoint_id).filled(:string).description("Endpoint ID (base64 URL-encoded)")
|
|
274
|
-
optional(:
|
|
275
|
-
optional(:
|
|
355
|
+
required(:endpoint_id).filled(:string).description("Endpoint ID (base64 URL-encoded). Extract from ScoutAPM URLs or use ParseScoutURLTool.")
|
|
356
|
+
optional(:range).maybe(:string).description("Quick time range template: 30min, 60min, 3hrs, 6hrs, 12hrs, 1day, 3days, 7days. If provided, calculates from/to automatically.")
|
|
357
|
+
optional(:from).maybe(:string).description("Start time in ISO 8601 format (e.g., 2025-11-17T15:25:35Z). Must be within last 7 days. Ignored if range is provided.")
|
|
358
|
+
optional(:to).maybe(:string).description("End time in ISO 8601 format (e.g., 2025-11-18T15:25:35Z). Used as end point for range if range is provided.")
|
|
276
359
|
end
|
|
277
360
|
|
|
278
|
-
def call(app_id:, endpoint_id:, from: nil, to: nil)
|
|
279
|
-
get_client.list_endpoint_traces(app_id, endpoint_id, from: from, to: to)
|
|
361
|
+
def call(app_id:, endpoint_id:, range: nil, from: nil, to: nil)
|
|
362
|
+
get_client.list_endpoint_traces(app_id, endpoint_id, from: from, to: to, range: range)
|
|
280
363
|
end
|
|
281
364
|
end
|
|
282
365
|
|
|
@@ -432,10 +515,28 @@ module ScoutApmMcp
|
|
|
432
515
|
|
|
433
516
|
# Utility Tools
|
|
434
517
|
class ParseScoutURLTool < BaseTool
|
|
435
|
-
description
|
|
518
|
+
description <<~DESC
|
|
519
|
+
Parse a ScoutAPM URL and extract resource information (app_id, endpoint_id, trace_id, etc.).
|
|
520
|
+
|
|
521
|
+
This tool extracts structured information from ScoutAPM URLs without making API calls.
|
|
522
|
+
Useful for extracting IDs before making other API requests.
|
|
523
|
+
|
|
524
|
+
Returns a hash with:
|
|
525
|
+
- url_type: :endpoint, :trace, :error_group, :insight, :app, or :unknown
|
|
526
|
+
- app_id: Application ID (integer)
|
|
527
|
+
- endpoint_id: Base64 URL-encoded endpoint ID (if present)
|
|
528
|
+
- trace_id: Trace ID (if present)
|
|
529
|
+
- error_id: Error group ID (if present)
|
|
530
|
+
- insight_type: Insight type (if present)
|
|
531
|
+
- decoded_endpoint: Human-readable endpoint path (if endpoint_id present)
|
|
532
|
+
|
|
533
|
+
Example:
|
|
534
|
+
- Input: "https://scoutapm.com/apps/123/endpoints/ABC123.../trace/456"
|
|
535
|
+
- Output: {url_type: :trace, app_id: 123, endpoint_id: "ABC123...", trace_id: 456, decoded_endpoint: "Controller/Action"}
|
|
536
|
+
DESC
|
|
436
537
|
|
|
437
538
|
arguments do
|
|
438
|
-
required(:url).filled(:string).description("Full ScoutAPM URL
|
|
539
|
+
required(:url).filled(:string).description("Full ScoutAPM URL")
|
|
439
540
|
end
|
|
440
541
|
|
|
441
542
|
def call(url:)
|
|
@@ -444,10 +545,27 @@ module ScoutApmMcp
|
|
|
444
545
|
end
|
|
445
546
|
|
|
446
547
|
class FetchScoutURLTool < BaseTool
|
|
447
|
-
description
|
|
548
|
+
description <<~DESC
|
|
549
|
+
Fetch data from a ScoutAPM URL by automatically detecting the resource type and fetching the appropriate data.
|
|
550
|
+
|
|
551
|
+
This tool automatically parses ScoutAPM URLs and fetches the corresponding data.
|
|
552
|
+
Supported URL types:
|
|
553
|
+
- Endpoint URLs: /apps/{app_id}/endpoints/{endpoint_id} (fetches from endpoint list)
|
|
554
|
+
- Trace URLs: /apps/{app_id}/endpoints/{endpoint_id}/trace/{trace_id}
|
|
555
|
+
- Error group URLs: /apps/{app_id}/error_groups/{error_id}
|
|
556
|
+
- Insight URLs: /apps/{app_id}/insights or /apps/{app_id}/insights/{insight_type}
|
|
557
|
+
- App URLs: /apps/{app_id}
|
|
558
|
+
|
|
559
|
+
Examples:
|
|
560
|
+
- https://scoutapm.com/apps/123/endpoints/ABC123... (endpoint)
|
|
561
|
+
- https://scoutapm.com/apps/123/endpoints/ABC123.../trace/456 (trace)
|
|
562
|
+
- https://scoutapm.com/apps/123/error_groups/789 (error group)
|
|
563
|
+
|
|
564
|
+
For trace URLs, set include_endpoint=true to also fetch endpoint context.
|
|
565
|
+
DESC
|
|
448
566
|
|
|
449
567
|
arguments do
|
|
450
|
-
required(:url).filled(:string).description("Full ScoutAPM URL
|
|
568
|
+
required(:url).filled(:string).description("Full ScoutAPM URL")
|
|
451
569
|
optional(:include_endpoint).filled(:bool).description("For trace URLs, also fetch endpoint details for context (default: false)")
|
|
452
570
|
end
|
|
453
571
|
|
|
@@ -469,11 +587,16 @@ module ScoutApmMcp
|
|
|
469
587
|
|
|
470
588
|
if include_endpoint && parsed[:endpoint_id]
|
|
471
589
|
begin
|
|
472
|
-
|
|
473
|
-
|
|
590
|
+
endpoints = client.list_endpoints(parsed[:app_id], range: "7days")
|
|
591
|
+
endpoint_data = endpoints.find { |ep| Helpers.get_endpoint_id(ep) == parsed[:endpoint_id] }
|
|
592
|
+
|
|
593
|
+
if endpoint_data
|
|
594
|
+
result[:data][:endpoint] = endpoint_data
|
|
595
|
+
else
|
|
596
|
+
result[:data][:endpoint_error] = "Endpoint not found in the last 7 days"
|
|
597
|
+
end
|
|
474
598
|
result[:data][:decoded_endpoint] = parsed[:decoded_endpoint]
|
|
475
599
|
rescue => e
|
|
476
|
-
# Endpoint fetch failed, but we still have trace data
|
|
477
600
|
result[:data][:endpoint_error] = "Failed to fetch endpoint: #{e.message}"
|
|
478
601
|
result[:data][:decoded_endpoint] = parsed[:decoded_endpoint]
|
|
479
602
|
end
|
|
@@ -483,11 +606,17 @@ module ScoutApmMcp
|
|
|
483
606
|
end
|
|
484
607
|
when :endpoint
|
|
485
608
|
if parsed[:app_id] && parsed[:endpoint_id]
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
609
|
+
endpoints = client.list_endpoints(parsed[:app_id], range: "7days")
|
|
610
|
+
endpoint_data = endpoints.find { |ep| Helpers.get_endpoint_id(ep) == parsed[:endpoint_id] }
|
|
611
|
+
|
|
612
|
+
if endpoint_data
|
|
613
|
+
result[:data] = {
|
|
614
|
+
endpoint: endpoint_data,
|
|
615
|
+
decoded_endpoint: parsed[:decoded_endpoint]
|
|
616
|
+
}
|
|
617
|
+
else
|
|
618
|
+
raise "Endpoint not found in the last 7 days. Try using ListEndpointsTool with a longer time range."
|
|
619
|
+
end
|
|
491
620
|
else
|
|
492
621
|
raise "Invalid endpoint URL: missing app_id or endpoint_id"
|
|
493
622
|
end
|
data/sig/scout_apm_mcp.rbs
CHANGED
|
@@ -16,7 +16,6 @@ module ScoutApmMcp
|
|
|
16
16
|
def list_metrics: (Integer app_id) -> Hash[String, untyped]
|
|
17
17
|
def get_metric: (Integer app_id, String metric_type, ?from: String?, ?to: String?) -> Hash[String, untyped]
|
|
18
18
|
def list_endpoints: (Integer app_id, ?from: String?, ?to: String?) -> Hash[String, untyped]
|
|
19
|
-
def get_endpoint: (Integer app_id, String endpoint_id) -> Hash[String, untyped]
|
|
20
19
|
def get_endpoint_metrics: (Integer app_id, String endpoint_id, String metric_type, ?from: String?, ?to: String?) -> Hash[String, untyped]
|
|
21
20
|
def list_endpoint_traces: (Integer app_id, String endpoint_id, ?from: String?, ?to: String?) -> Hash[String, untyped]
|
|
22
21
|
def fetch_trace: (Integer app_id, Integer trace_id) -> Hash[String, untyped]
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: scout_apm_mcp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Makarov
|
|
@@ -47,22 +47,22 @@ dependencies:
|
|
|
47
47
|
name: rack
|
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|
|
49
49
|
requirements:
|
|
50
|
-
- - "
|
|
50
|
+
- - ">="
|
|
51
51
|
- !ruby/object:Gem::Version
|
|
52
52
|
version: '2.2'
|
|
53
|
-
- - "
|
|
53
|
+
- - "<"
|
|
54
54
|
- !ruby/object:Gem::Version
|
|
55
|
-
version:
|
|
55
|
+
version: '4.0'
|
|
56
56
|
type: :runtime
|
|
57
57
|
prerelease: false
|
|
58
58
|
version_requirements: !ruby/object:Gem::Requirement
|
|
59
59
|
requirements:
|
|
60
|
-
- - "
|
|
60
|
+
- - ">="
|
|
61
61
|
- !ruby/object:Gem::Version
|
|
62
62
|
version: '2.2'
|
|
63
|
-
- - "
|
|
63
|
+
- - "<"
|
|
64
64
|
- !ruby/object:Gem::Version
|
|
65
|
-
version:
|
|
65
|
+
version: '4.0'
|
|
66
66
|
- !ruby/object:Gem::Dependency
|
|
67
67
|
name: opdotenv
|
|
68
68
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -175,6 +175,48 @@ dependencies:
|
|
|
175
175
|
- - "~>"
|
|
176
176
|
- !ruby/object:Gem::Version
|
|
177
177
|
version: '1.52'
|
|
178
|
+
- !ruby/object:Gem::Dependency
|
|
179
|
+
name: standard-performance
|
|
180
|
+
requirement: !ruby/object:Gem::Requirement
|
|
181
|
+
requirements:
|
|
182
|
+
- - "~>"
|
|
183
|
+
- !ruby/object:Gem::Version
|
|
184
|
+
version: '1.8'
|
|
185
|
+
type: :development
|
|
186
|
+
prerelease: false
|
|
187
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
188
|
+
requirements:
|
|
189
|
+
- - "~>"
|
|
190
|
+
- !ruby/object:Gem::Version
|
|
191
|
+
version: '1.8'
|
|
192
|
+
- !ruby/object:Gem::Dependency
|
|
193
|
+
name: standard-rspec
|
|
194
|
+
requirement: !ruby/object:Gem::Requirement
|
|
195
|
+
requirements:
|
|
196
|
+
- - "~>"
|
|
197
|
+
- !ruby/object:Gem::Version
|
|
198
|
+
version: '0.3'
|
|
199
|
+
type: :development
|
|
200
|
+
prerelease: false
|
|
201
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
202
|
+
requirements:
|
|
203
|
+
- - "~>"
|
|
204
|
+
- !ruby/object:Gem::Version
|
|
205
|
+
version: '0.3'
|
|
206
|
+
- !ruby/object:Gem::Dependency
|
|
207
|
+
name: rubocop-rspec
|
|
208
|
+
requirement: !ruby/object:Gem::Requirement
|
|
209
|
+
requirements:
|
|
210
|
+
- - "~>"
|
|
211
|
+
- !ruby/object:Gem::Version
|
|
212
|
+
version: '3.8'
|
|
213
|
+
type: :development
|
|
214
|
+
prerelease: false
|
|
215
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
216
|
+
requirements:
|
|
217
|
+
- - "~>"
|
|
218
|
+
- !ruby/object:Gem::Version
|
|
219
|
+
version: '3.8'
|
|
178
220
|
- !ruby/object:Gem::Dependency
|
|
179
221
|
name: appraisal
|
|
180
222
|
requirement: !ruby/object:Gem::Requirement
|