scout_apm_mcp 0.1.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 +13 -0
- data/LICENSE.md +22 -0
- data/README.md +322 -0
- data/bin/scout_apm_mcp +18 -0
- data/lib/scout_apm_mcp/client.rb +302 -0
- data/lib/scout_apm_mcp/helpers.rb +130 -0
- data/lib/scout_apm_mcp/server.rb +502 -0
- data/lib/scout_apm_mcp/version.rb +3 -0
- data/lib/scout_apm_mcp.rb +36 -0
- data/sig/scout_apm_mcp.rbs +33 -0
- metadata +264 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 94c095f06ab792c7e66a4a3d7893a0476308730d1d346f661ba34d52be5b665b
|
|
4
|
+
data.tar.gz: c1823c4b464ab833663a3026ff3f1c1d90de4c6c3b832f57257ce1f495c161e3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: adba47ba7ab8d8838e42c236afe334243425f38701da5467e78badd91195ed029dfe4f5c3b308903c67f118ef10778a5e14dbd4dd4f8feb9801a3778da4d20d0
|
|
7
|
+
data.tar.gz: 657457f4239d0531bf7cac76fcd15e5ac9416947fd24d9ce83e38a74c47d0583a41b8194ae526d915ab540c0a3fb56c50cb0c111a22e5f9a23197c725550031a
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# CHANGELOG
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2025-11-20)
|
|
4
|
+
|
|
5
|
+
- Initial release
|
|
6
|
+
- ScoutAPM API client with full endpoint coverage (applications, metrics, endpoints, traces, errors, insights, OpenAPI)
|
|
7
|
+
- MCP server integration for Cursor IDE with executable `bundle exec scout_apm_mcp`
|
|
8
|
+
- Helper methods for API key management and URL parsing (`Helpers.get_api_key`, `Helpers.parse_scout_url`, `Helpers.decode_endpoint_id`)
|
|
9
|
+
- Support for environment variables and 1Password integration (via optional `opdotenv` gem)
|
|
10
|
+
- Complete RBS type signatures for all public APIs
|
|
11
|
+
- Comprehensive test suite with RSpec
|
|
12
|
+
- Requires Ruby 3.0 or higher
|
|
13
|
+
- All dependencies use latest compatible versions with pessimistic versioning for security
|
data/LICENSE.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
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.
|
|
22
|
+
|
data/README.md
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# scout_apm_mcp
|
|
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
|
+
|
|
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
|
+
|
|
7
|
+
Sponsored by [Kisko Labs](https://www.kiskolabs.com).
|
|
8
|
+
|
|
9
|
+
<a href="https://www.kiskolabs.com">
|
|
10
|
+
<img src="kisko.svg" width="200" alt="Sponsored by Kisko Labs" />
|
|
11
|
+
</a>
|
|
12
|
+
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
- **Ruby 3.1 or higher** (Ruby 3.0 and earlier are not supported)
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
1. In scoutapm create API key under Organization settings: https://scoutapm.com/settings
|
|
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
|
+
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
|
+
|
|
23
|
+
### Cursor IDE Configuration
|
|
24
|
+
|
|
25
|
+
For Cursor IDE, create or update `.cursor/mcp.json` in your project:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"scout-apm": {
|
|
31
|
+
"command": "bundle",
|
|
32
|
+
"args": ["exec", "scout_apm_mcp"],
|
|
33
|
+
"env": {
|
|
34
|
+
"OP_ENV_ENTRY_PATH": "op://Vault Name/Item Name"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or if installed globally:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"scout-apm": {
|
|
47
|
+
"command": "scout_apm_mcp",
|
|
48
|
+
"env": {
|
|
49
|
+
"OP_ENV_ENTRY_PATH": "op://Vault Name/Item Name"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Claude Desktop Configuration
|
|
57
|
+
|
|
58
|
+
For Claude Desktop, edit the MCP configuration file:
|
|
59
|
+
|
|
60
|
+
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
61
|
+
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"mcpServers": {
|
|
66
|
+
"scout-apm": {
|
|
67
|
+
"command": "bundle",
|
|
68
|
+
"args": ["exec", "scout_apm_mcp"],
|
|
69
|
+
"cwd": "/path/to/your/project"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Or if installed globally:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"mcpServers": {
|
|
80
|
+
"scout-apm": {
|
|
81
|
+
"command": "scout_apm_mcp"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Note**: After updating the configuration, restart Claude Desktop for changes to take effect.
|
|
88
|
+
|
|
89
|
+
### Security Best Practice
|
|
90
|
+
|
|
91
|
+
Do not store API keys or tokens in MCP configuration files. Instead, use one of these methods:
|
|
92
|
+
|
|
93
|
+
1. **1Password Integration**: Set `OP_ENV_ENTRY_PATH` environment variable (e.g., `op://Vault/Item`) to automatically load credentials via opdotenv
|
|
94
|
+
2. **1Password CLI**: The gem will automatically fall back to 1Password CLI if opdotenv is not available
|
|
95
|
+
3. **Environment Variables**: Set `API_KEY` or `SCOUT_APM_API_KEY` in your shell environment (not recommended for production - use secret vault for in-memory provisioning)
|
|
96
|
+
|
|
97
|
+
The gem will automatically detect and use credentials from your environment or 1Password integration.
|
|
98
|
+
|
|
99
|
+
### Testing with MCP Inspector
|
|
100
|
+
|
|
101
|
+
You can test the MCP server using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) tool:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# Set your 1Password entry path (or use API_KEY/SCOUT_APM_API_KEY)
|
|
105
|
+
export OP_ENV_ENTRY_PATH="op://Vault/Scout APM"
|
|
106
|
+
|
|
107
|
+
# Run the MCP inspector with the server
|
|
108
|
+
npx @modelcontextprotocol/inspector bundle exec scout_apm_mcp
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The inspector will:
|
|
112
|
+
1. Start a proxy server and open a browser interface
|
|
113
|
+
2. Connect to your MCP server via STDIO
|
|
114
|
+
3. Allow you to test all available tools interactively
|
|
115
|
+
4. Display request/response messages and any errors
|
|
116
|
+
|
|
117
|
+
This is useful for:
|
|
118
|
+
- Testing tool functionality before integrating with MCP clients
|
|
119
|
+
- Debugging MCP protocol communication
|
|
120
|
+
- Verifying API key configuration
|
|
121
|
+
- Exploring available tools and their parameters
|
|
122
|
+
|
|
123
|
+
### Running the MCP Server manually
|
|
124
|
+
|
|
125
|
+
After installation, you can start the MCP server immediately:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# With bundler
|
|
129
|
+
gem install scout_apm_mcp && bundle exec scout_apm_mcp
|
|
130
|
+
|
|
131
|
+
# Or if installed globally
|
|
132
|
+
scout_apm_mcp
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
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).
|
|
136
|
+
|
|
137
|
+
## Features
|
|
138
|
+
|
|
139
|
+
- **ScoutAPM API Client**: Full-featured client for ScoutAPM REST API
|
|
140
|
+
- **MCP Server Integration**: Ready-to-use MCP server compatible with Cursor IDE, Claude Desktop, and other MCP-enabled tools
|
|
141
|
+
- **API Key Management**: Supports environment variables and 1Password integration (via optional `opdotenv` gem)
|
|
142
|
+
- **URL Parsing**: Helper methods to parse ScoutAPM URLs and extract IDs
|
|
143
|
+
- **Comprehensive API Coverage**: Supports all ScoutAPM API endpoints (apps, metrics, endpoints, traces, errors, insights)
|
|
144
|
+
|
|
145
|
+
## Basic Usage
|
|
146
|
+
|
|
147
|
+
### API Client
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
require "scout_apm_mcp"
|
|
151
|
+
|
|
152
|
+
# Get API key (from environment or 1Password)
|
|
153
|
+
api_key = ScoutApmMcp::Helpers.get_api_key
|
|
154
|
+
|
|
155
|
+
# Create client
|
|
156
|
+
client = ScoutApmMcp::Client.new(api_key: api_key)
|
|
157
|
+
|
|
158
|
+
# List applications
|
|
159
|
+
apps = client.list_apps
|
|
160
|
+
|
|
161
|
+
# Get application details
|
|
162
|
+
app = client.get_app(123)
|
|
163
|
+
|
|
164
|
+
# List endpoints
|
|
165
|
+
endpoints = client.list_endpoints(123)
|
|
166
|
+
|
|
167
|
+
# Fetch trace
|
|
168
|
+
trace = client.fetch_trace(123, 456)
|
|
169
|
+
|
|
170
|
+
# Get metrics
|
|
171
|
+
metrics = client.get_metric(123, "response_time", from: "2025-01-01T00:00:00Z", to: "2025-01-02T00:00:00Z")
|
|
172
|
+
|
|
173
|
+
# List error groups
|
|
174
|
+
errors = client.list_error_groups(123, from: "2025-01-01T00:00:00Z", to: "2025-01-02T00:00:00Z")
|
|
175
|
+
|
|
176
|
+
# Get insights
|
|
177
|
+
insights = client.get_all_insights(123, limit: 20)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### URL Parsing
|
|
181
|
+
|
|
182
|
+
```ruby
|
|
183
|
+
# Parse a ScoutAPM trace URL
|
|
184
|
+
url = "https://scoutapm.com/apps/123/endpoints/.../trace/456"
|
|
185
|
+
parsed = ScoutApmMcp::Helpers.parse_scout_url(url)
|
|
186
|
+
# => { app_id: 123, endpoint_id: "...", trace_id: 456, decoded_endpoint: "...", query_params: {...} }
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### API Key Management
|
|
190
|
+
|
|
191
|
+
The gem supports multiple methods for API key retrieval (checked in order):
|
|
192
|
+
|
|
193
|
+
1. **Direct parameter**: Pass `api_key:` when calling `Helpers.get_api_key`
|
|
194
|
+
2. **Environment variable**: Set `API_KEY` or `SCOUT_APM_API_KEY`
|
|
195
|
+
3. **1Password via OP_ENV_ENTRY_PATH**: Set `OP_ENV_ENTRY_PATH` environment variable (e.g., `op://Vault/Item`)
|
|
196
|
+
4. **1Password via opdotenv**: Automatically loads from 1Password if `opdotenv` gem is available and `op_vault`/`op_item` are provided
|
|
197
|
+
5. **1Password CLI**: Falls back to direct `op` CLI command
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
# From environment variable (recommended: use in-memory vault or shell environment)
|
|
201
|
+
# Set API_KEY or SCOUT_APM_API_KEY in your environment
|
|
202
|
+
api_key = ScoutApmMcp::Helpers.get_api_key
|
|
203
|
+
|
|
204
|
+
# From 1Password using OP_ENV_ENTRY_PATH (recommended for 1Password users)
|
|
205
|
+
# Set OP_ENV_ENTRY_PATH in your environment (e.g., op://Vault/Item)
|
|
206
|
+
ENV["OP_ENV_ENTRY_PATH"] = "op://YourVault/YourItem"
|
|
207
|
+
api_key = ScoutApmMcp::Helpers.get_api_key
|
|
208
|
+
|
|
209
|
+
# From 1Password with explicit vault/item (requires opdotenv gem or op CLI)
|
|
210
|
+
api_key = ScoutApmMcp::Helpers.get_api_key(
|
|
211
|
+
op_vault: "YourVault",
|
|
212
|
+
op_item: "Your ScoutAPM API",
|
|
213
|
+
op_field: "API_KEY"
|
|
214
|
+
)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Security Note**: Never hardcode API keys in your code or configuration files. Always use environment variables, in-memory vaults, or secure credential management systems like 1Password.
|
|
218
|
+
|
|
219
|
+
## API Methods
|
|
220
|
+
|
|
221
|
+
### Applications
|
|
222
|
+
|
|
223
|
+
- `list_apps` - List all applications
|
|
224
|
+
- `get_app(app_id)` - Get application details
|
|
225
|
+
|
|
226
|
+
### Metrics
|
|
227
|
+
|
|
228
|
+
- `list_metrics(app_id)` - List available metric types
|
|
229
|
+
- `get_metric(app_id, metric_type, from:, to:)` - Get time-series metric data
|
|
230
|
+
|
|
231
|
+
### Endpoints
|
|
232
|
+
|
|
233
|
+
- `list_endpoints(app_id, from:, to:)` - List all endpoints
|
|
234
|
+
- `get_endpoint(app_id, endpoint_id)` - Get endpoint details
|
|
235
|
+
- `get_endpoint_metrics(app_id, endpoint_id, metric_type, from:, to:)` - Get endpoint metrics
|
|
236
|
+
- `list_endpoint_traces(app_id, endpoint_id, from:, to:)` - List endpoint traces
|
|
237
|
+
|
|
238
|
+
### Traces
|
|
239
|
+
|
|
240
|
+
- `fetch_trace(app_id, trace_id)` - Fetch detailed trace information
|
|
241
|
+
|
|
242
|
+
### Errors
|
|
243
|
+
|
|
244
|
+
- `list_error_groups(app_id, from:, to:, endpoint:)` - List error groups
|
|
245
|
+
- `get_error_group(app_id, error_id)` - Get error group details
|
|
246
|
+
- `get_error_group_errors(app_id, error_id)` - Get errors within a group
|
|
247
|
+
|
|
248
|
+
### Insights
|
|
249
|
+
|
|
250
|
+
- `get_all_insights(app_id, limit:)` - Get all insight types
|
|
251
|
+
- `get_insight_by_type(app_id, insight_type, limit:)` - Get specific insight type
|
|
252
|
+
- `get_insights_history(app_id, from:, to:, limit:, pagination_cursor:, pagination_direction:, pagination_page:)` - Get historical insights
|
|
253
|
+
- `get_insights_history_by_type(app_id, insight_type, from:, to:, limit:, pagination_cursor:, pagination_direction:, pagination_page:)` - Get historical insights by type
|
|
254
|
+
|
|
255
|
+
### OpenAPI Schema
|
|
256
|
+
|
|
257
|
+
- `fetch_openapi_schema` - Fetch the ScoutAPM OpenAPI schema
|
|
258
|
+
|
|
259
|
+
## MCP Server Integration
|
|
260
|
+
|
|
261
|
+
This gem includes a ready-to-use MCP server that can be run directly:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
# After installing the gem
|
|
265
|
+
bundle exec scout_apm_mcp
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Or if installed globally:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
gem install scout_apm_mcp
|
|
272
|
+
scout_apm_mcp
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
The server will communicate via STDIN/STDOUT using the MCP protocol. Configure it in your MCP client (e.g., Cursor IDE, Claude Desktop, or other MCP-enabled tools).
|
|
276
|
+
|
|
277
|
+
## Error Handling
|
|
278
|
+
|
|
279
|
+
The client raises exceptions for API errors:
|
|
280
|
+
|
|
281
|
+
- `RuntimeError` with message containing "Authentication failed" for 401 Unauthorized
|
|
282
|
+
- `RuntimeError` with message containing "Resource not found" for 404 Not Found
|
|
283
|
+
- `RuntimeError` with message containing "API request failed" for other HTTP errors
|
|
284
|
+
|
|
285
|
+
## Development
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
# Install dependencies
|
|
289
|
+
bundle install
|
|
290
|
+
|
|
291
|
+
# Run tests
|
|
292
|
+
bundle exec rspec
|
|
293
|
+
|
|
294
|
+
# Run tests across multiple Ruby versions
|
|
295
|
+
bundle exec appraisal install
|
|
296
|
+
bundle exec appraisal rspec
|
|
297
|
+
|
|
298
|
+
# Run linting
|
|
299
|
+
bundle exec standardrb --fix
|
|
300
|
+
|
|
301
|
+
# Validate RBS type signatures
|
|
302
|
+
bundle exec rbs validate
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Contributing
|
|
306
|
+
|
|
307
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/amkisko/scout_apm_mcp.rb.
|
|
308
|
+
|
|
309
|
+
Contribution policy:
|
|
310
|
+
- New features are not necessarily added to the gem
|
|
311
|
+
- Pull request should have test coverage for affected parts
|
|
312
|
+
- Pull request should have changelog entry
|
|
313
|
+
|
|
314
|
+
Review policy:
|
|
315
|
+
- It might take up to 2 calendar weeks to review and merge critical fixes
|
|
316
|
+
- It might take up to 6 calendar months to review and merge pull request
|
|
317
|
+
- It might take up to 1 calendar year to review an issue
|
|
318
|
+
|
|
319
|
+
## License
|
|
320
|
+
|
|
321
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE.md).
|
|
322
|
+
|
data/bin/scout_apm_mcp
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Suppress all output to prevent breaking JSON parsing in MCP clients
|
|
5
|
+
# STDOUT is used for MCP protocol communication (JSON-RPC), so we must not output anything
|
|
6
|
+
# STDERR is also suppressed to prevent any error messages from breaking JSON parsing
|
|
7
|
+
unless ENV["DEBUG"]
|
|
8
|
+
require "stringio"
|
|
9
|
+
# Redirect stderr to StringIO to suppress all error output
|
|
10
|
+
$stderr = StringIO.new
|
|
11
|
+
$stderr.set_encoding("UTF-8")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
require "scout_apm_mcp"
|
|
15
|
+
require "scout_apm_mcp/server"
|
|
16
|
+
|
|
17
|
+
ScoutApmMcp::Server.start
|
|
18
|
+
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
require "uri"
|
|
2
|
+
require "net/http"
|
|
3
|
+
require "json"
|
|
4
|
+
require "base64"
|
|
5
|
+
|
|
6
|
+
module ScoutApmMcp
|
|
7
|
+
# ScoutAPM API client for making authenticated requests to the ScoutAPM API
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# api_key = ScoutApmMcp::Helpers.get_api_key
|
|
11
|
+
# client = ScoutApmMcp::Client.new(api_key: api_key)
|
|
12
|
+
# apps = client.list_apps
|
|
13
|
+
# trace = client.fetch_trace(123, 456)
|
|
14
|
+
class Client
|
|
15
|
+
API_BASE = "https://scoutapm.com/api/v0"
|
|
16
|
+
|
|
17
|
+
# @param api_key [String] ScoutAPM API key
|
|
18
|
+
# @param api_base [String] API base URL (default: https://scoutapm.com/api/v0)
|
|
19
|
+
def initialize(api_key:, api_base: API_BASE)
|
|
20
|
+
@api_key = api_key
|
|
21
|
+
@api_base = api_base
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# List all applications accessible with the provided API key
|
|
25
|
+
#
|
|
26
|
+
# @return [Hash] API response containing applications list
|
|
27
|
+
def list_apps
|
|
28
|
+
uri = URI("#{@api_base}/apps")
|
|
29
|
+
make_request(uri)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Get application details for a specific application
|
|
33
|
+
#
|
|
34
|
+
# @param app_id [Integer] ScoutAPM application ID
|
|
35
|
+
# @return [Hash] API response containing application details
|
|
36
|
+
def get_app(app_id)
|
|
37
|
+
uri = URI("#{@api_base}/apps/#{app_id}")
|
|
38
|
+
make_request(uri)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# List available metric types for an application
|
|
42
|
+
#
|
|
43
|
+
# @param app_id [Integer] ScoutAPM application ID
|
|
44
|
+
# @return [Hash] API response containing available metrics
|
|
45
|
+
def list_metrics(app_id)
|
|
46
|
+
uri = URI("#{@api_base}/apps/#{app_id}/metrics")
|
|
47
|
+
make_request(uri)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Get time-series data for a specific metric type
|
|
51
|
+
#
|
|
52
|
+
# @param app_id [Integer] ScoutAPM application ID
|
|
53
|
+
# @param metric_type [String] Metric type (apdex, response_time, response_time_95th, errors, throughput, queue_time)
|
|
54
|
+
# @param from [String, nil] Start time in ISO 8601 format
|
|
55
|
+
# @param to [String, nil] End time in ISO 8601 format
|
|
56
|
+
# @return [Hash] API response containing metric data
|
|
57
|
+
def get_metric(app_id, metric_type, from: nil, to: nil)
|
|
58
|
+
uri = URI("#{@api_base}/apps/#{app_id}/metrics/#{metric_type}")
|
|
59
|
+
uri.query = build_query_string(from: from, to: to)
|
|
60
|
+
make_request(uri)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# List all endpoints for an application
|
|
64
|
+
#
|
|
65
|
+
# @param app_id [Integer] ScoutAPM application ID
|
|
66
|
+
# @param from [String, nil] Start time in ISO 8601 format
|
|
67
|
+
# @param to [String, nil] End time in ISO 8601 format
|
|
68
|
+
# @return [Hash] API response containing endpoints list
|
|
69
|
+
def list_endpoints(app_id, from: nil, to: nil)
|
|
70
|
+
uri = URI("#{@api_base}/apps/#{app_id}/endpoints")
|
|
71
|
+
uri.query = build_query_string(from: from, to: to)
|
|
72
|
+
make_request(uri)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Get endpoint details
|
|
76
|
+
#
|
|
77
|
+
# @param app_id [Integer] ScoutAPM application ID
|
|
78
|
+
# @param endpoint_id [String] Endpoint ID (base64 URL-encoded)
|
|
79
|
+
# @return [Hash] API response containing endpoint details
|
|
80
|
+
def get_endpoint(app_id, endpoint_id)
|
|
81
|
+
encoded_endpoint_id = URI.encode_www_form_component(endpoint_id)
|
|
82
|
+
uri = URI("#{@api_base}/apps/#{app_id}/endpoints/#{encoded_endpoint_id}")
|
|
83
|
+
make_request(uri)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Get metric data for a specific endpoint
|
|
87
|
+
#
|
|
88
|
+
# @param app_id [Integer] ScoutAPM application ID
|
|
89
|
+
# @param endpoint_id [String] Endpoint ID (base64 URL-encoded)
|
|
90
|
+
# @param metric_type [String] Metric type (apdex, response_time, response_time_95th, errors, throughput, queue_time)
|
|
91
|
+
# @param from [String, nil] Start time in ISO 8601 format
|
|
92
|
+
# @param to [String, nil] End time in ISO 8601 format
|
|
93
|
+
# @return [Hash] API response containing endpoint metrics
|
|
94
|
+
def get_endpoint_metrics(app_id, endpoint_id, metric_type, from: nil, to: nil)
|
|
95
|
+
encoded_endpoint_id = URI.encode_www_form_component(endpoint_id)
|
|
96
|
+
uri = URI("#{@api_base}/apps/#{app_id}/endpoints/#{encoded_endpoint_id}/metrics/#{metric_type}")
|
|
97
|
+
uri.query = build_query_string(from: from, to: to)
|
|
98
|
+
make_request(uri)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# List traces for a specific endpoint (max 100, within 7 days)
|
|
102
|
+
#
|
|
103
|
+
# @param app_id [Integer] ScoutAPM application ID
|
|
104
|
+
# @param endpoint_id [String] Endpoint ID (base64 URL-encoded)
|
|
105
|
+
# @param from [String, nil] Start time in ISO 8601 format
|
|
106
|
+
# @param to [String, nil] End time in ISO 8601 format
|
|
107
|
+
# @return [Hash] API response containing traces list
|
|
108
|
+
def list_endpoint_traces(app_id, endpoint_id, from: nil, to: nil)
|
|
109
|
+
encoded_endpoint_id = URI.encode_www_form_component(endpoint_id)
|
|
110
|
+
uri = URI("#{@api_base}/apps/#{app_id}/endpoints/#{encoded_endpoint_id}/traces")
|
|
111
|
+
uri.query = build_query_string(from: from, to: to)
|
|
112
|
+
make_request(uri)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Fetch detailed trace information
|
|
116
|
+
#
|
|
117
|
+
# @param app_id [Integer] ScoutAPM application ID
|
|
118
|
+
# @param trace_id [Integer] Trace identifier
|
|
119
|
+
# @return [Hash] API response containing trace details
|
|
120
|
+
def fetch_trace(app_id, trace_id)
|
|
121
|
+
uri = URI("#{@api_base}/apps/#{app_id}/traces/#{trace_id}")
|
|
122
|
+
make_request(uri)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# List error groups for an application (max 100, within 30 days)
|
|
126
|
+
#
|
|
127
|
+
# @param app_id [Integer] ScoutAPM application ID
|
|
128
|
+
# @param from [String, nil] Start time in ISO 8601 format
|
|
129
|
+
# @param to [String, nil] End time in ISO 8601 format
|
|
130
|
+
# @param endpoint [String, nil] Base64 URL-encoded endpoint filter (optional)
|
|
131
|
+
# @return [Hash] API response containing error groups list
|
|
132
|
+
def list_error_groups(app_id, from: nil, to: nil, endpoint: nil)
|
|
133
|
+
uri = URI("#{@api_base}/apps/#{app_id}/error_groups")
|
|
134
|
+
params = {}
|
|
135
|
+
params["from"] = from if from
|
|
136
|
+
params["to"] = to if to
|
|
137
|
+
params["endpoint"] = endpoint if endpoint
|
|
138
|
+
uri.query = URI.encode_www_form(params) unless params.empty?
|
|
139
|
+
make_request(uri)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Get details for a specific error group
|
|
143
|
+
#
|
|
144
|
+
# @param app_id [Integer] ScoutAPM application ID
|
|
145
|
+
# @param error_id [Integer] Error group identifier
|
|
146
|
+
# @return [Hash] API response containing error group details
|
|
147
|
+
def get_error_group(app_id, error_id)
|
|
148
|
+
uri = URI("#{@api_base}/apps/#{app_id}/error_groups/#{error_id}")
|
|
149
|
+
make_request(uri)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Get individual errors within an error group (max 100)
|
|
153
|
+
#
|
|
154
|
+
# @param app_id [Integer] ScoutAPM application ID
|
|
155
|
+
# @param error_id [Integer] Error group identifier
|
|
156
|
+
# @return [Hash] API response containing errors list
|
|
157
|
+
def get_error_group_errors(app_id, error_id)
|
|
158
|
+
uri = URI("#{@api_base}/apps/#{app_id}/error_groups/#{error_id}/errors")
|
|
159
|
+
make_request(uri)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Get all insight types for an application (cached for 5 minutes)
|
|
163
|
+
#
|
|
164
|
+
# @param app_id [Integer] ScoutAPM application ID
|
|
165
|
+
# @param limit [Integer, nil] Maximum number of items per insight type (default: 20)
|
|
166
|
+
# @return [Hash] API response containing all insights
|
|
167
|
+
def get_all_insights(app_id, limit: nil)
|
|
168
|
+
uri = URI("#{@api_base}/apps/#{app_id}/insights")
|
|
169
|
+
uri.query = "limit=#{limit}" if limit
|
|
170
|
+
make_request(uri)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Get data for a specific insight type
|
|
174
|
+
#
|
|
175
|
+
# @param app_id [Integer] ScoutAPM application ID
|
|
176
|
+
# @param insight_type [String] Insight type (n_plus_one, memory_bloat, slow_query)
|
|
177
|
+
# @param limit [Integer, nil] Maximum number of items (default: 20)
|
|
178
|
+
# @return [Hash] API response containing insights
|
|
179
|
+
def get_insight_by_type(app_id, insight_type, limit: nil)
|
|
180
|
+
uri = URI("#{@api_base}/apps/#{app_id}/insights/#{insight_type}")
|
|
181
|
+
uri.query = "limit=#{limit}" if limit
|
|
182
|
+
make_request(uri)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Get historical insights data with cursor-based pagination
|
|
186
|
+
#
|
|
187
|
+
# @param app_id [Integer] ScoutAPM application ID
|
|
188
|
+
# @param from [String, nil] Start time in ISO 8601 format
|
|
189
|
+
# @param to [String, nil] End time in ISO 8601 format
|
|
190
|
+
# @param limit [Integer, nil] Maximum number of items per page (default: 10)
|
|
191
|
+
# @param pagination_cursor [Integer, nil] Cursor for pagination (insight ID)
|
|
192
|
+
# @param pagination_direction [String, nil] Pagination direction (forward, backward)
|
|
193
|
+
# @param pagination_page [Integer, nil] Page number for pagination (default: 1)
|
|
194
|
+
# @return [Hash] API response containing historical insights
|
|
195
|
+
def get_insights_history(app_id, from: nil, to: nil, limit: nil, pagination_cursor: nil, pagination_direction: nil, pagination_page: nil)
|
|
196
|
+
uri = URI("#{@api_base}/apps/#{app_id}/insights/history")
|
|
197
|
+
params = {}
|
|
198
|
+
params["from"] = from if from
|
|
199
|
+
params["to"] = to if to
|
|
200
|
+
params["limit"] = limit if limit
|
|
201
|
+
params["pagination_cursor"] = pagination_cursor if pagination_cursor
|
|
202
|
+
params["pagination_direction"] = pagination_direction if pagination_direction
|
|
203
|
+
params["pagination_page"] = pagination_page if pagination_page
|
|
204
|
+
uri.query = URI.encode_www_form(params) unless params.empty?
|
|
205
|
+
make_request(uri)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Get historical insights data filtered by insight type with cursor-based pagination
|
|
209
|
+
#
|
|
210
|
+
# @param app_id [Integer] ScoutAPM application ID
|
|
211
|
+
# @param insight_type [String] Insight type (n_plus_one, memory_bloat, slow_query)
|
|
212
|
+
# @param from [String, nil] Start time in ISO 8601 format
|
|
213
|
+
# @param to [String, nil] End time in ISO 8601 format
|
|
214
|
+
# @param limit [Integer, nil] Maximum number of items per page (default: 10)
|
|
215
|
+
# @param pagination_cursor [Integer, nil] Cursor for pagination (insight ID)
|
|
216
|
+
# @param pagination_direction [String, nil] Pagination direction (forward, backward)
|
|
217
|
+
# @param pagination_page [Integer, nil] Page number for pagination (default: 1)
|
|
218
|
+
# @return [Hash] API response containing historical insights
|
|
219
|
+
def get_insights_history_by_type(app_id, insight_type, from: nil, to: nil, limit: nil, pagination_cursor: nil, pagination_direction: nil, pagination_page: nil)
|
|
220
|
+
uri = URI("#{@api_base}/apps/#{app_id}/insights/history/#{insight_type}")
|
|
221
|
+
params = {}
|
|
222
|
+
params["from"] = from if from
|
|
223
|
+
params["to"] = to if to
|
|
224
|
+
params["limit"] = limit if limit
|
|
225
|
+
params["pagination_cursor"] = pagination_cursor if pagination_cursor
|
|
226
|
+
params["pagination_direction"] = pagination_direction if pagination_direction
|
|
227
|
+
params["pagination_page"] = pagination_page if pagination_page
|
|
228
|
+
uri.query = URI.encode_www_form(params) unless params.empty?
|
|
229
|
+
make_request(uri)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Fetch the ScoutAPM OpenAPI schema
|
|
233
|
+
#
|
|
234
|
+
# @return [Hash] Hash containing :content, :content_type, and :status
|
|
235
|
+
def fetch_openapi_schema
|
|
236
|
+
uri = URI("https://scoutapm.com/api/v0/openapi.yaml")
|
|
237
|
+
http = build_http_client(uri)
|
|
238
|
+
|
|
239
|
+
request = Net::HTTP::Get.new(uri)
|
|
240
|
+
request["X-SCOUT-API"] = @api_key
|
|
241
|
+
request["Accept"] = "application/x-yaml, application/yaml, text/yaml, */*"
|
|
242
|
+
|
|
243
|
+
response = http.request(request)
|
|
244
|
+
|
|
245
|
+
case response
|
|
246
|
+
when Net::HTTPSuccess
|
|
247
|
+
{
|
|
248
|
+
content: response.body,
|
|
249
|
+
content_type: response.content_type,
|
|
250
|
+
status: response.code.to_i
|
|
251
|
+
}
|
|
252
|
+
when Net::HTTPUnauthorized
|
|
253
|
+
raise "Authentication failed. Check your API key."
|
|
254
|
+
else
|
|
255
|
+
raise "API request failed: #{response.code} #{response.message}"
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
private
|
|
260
|
+
|
|
261
|
+
# Build HTTP client
|
|
262
|
+
#
|
|
263
|
+
# @param uri [URI] URI object for the request
|
|
264
|
+
# @return [Net::HTTP] Configured HTTP client
|
|
265
|
+
def build_http_client(uri)
|
|
266
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
267
|
+
http.use_ssl = uri.scheme == "https"
|
|
268
|
+
http.read_timeout = 10
|
|
269
|
+
http.open_timeout = 10
|
|
270
|
+
http
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def build_query_string(from: nil, to: nil)
|
|
274
|
+
params = {}
|
|
275
|
+
params["from"] = from if from
|
|
276
|
+
params["to"] = to if to
|
|
277
|
+
return nil if params.empty?
|
|
278
|
+
URI.encode_www_form(params)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def make_request(uri)
|
|
282
|
+
http = build_http_client(uri)
|
|
283
|
+
|
|
284
|
+
request = Net::HTTP::Get.new(uri)
|
|
285
|
+
request["X-SCOUT-API"] = @api_key
|
|
286
|
+
request["Accept"] = "application/json"
|
|
287
|
+
|
|
288
|
+
response = http.request(request)
|
|
289
|
+
|
|
290
|
+
case response
|
|
291
|
+
when Net::HTTPSuccess
|
|
292
|
+
JSON.parse(response.body)
|
|
293
|
+
when Net::HTTPUnauthorized
|
|
294
|
+
raise "Authentication failed. Check your API key. Response: #{response.body}"
|
|
295
|
+
when Net::HTTPNotFound
|
|
296
|
+
raise "Resource not found. Response: #{response.body}"
|
|
297
|
+
else
|
|
298
|
+
raise "API request failed: #{response.code} #{response.message}\n#{response.body}"
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
end
|