@smartbear/mcp 0.4.0 → 0.6.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.
- package/README.md +15 -121
- package/dist/{insight-hub → bugsnag}/client/api/CurrentUser.js +4 -4
- package/dist/{insight-hub → bugsnag}/client/api/Error.js +37 -4
- package/dist/bugsnag/client/api/Project.js +163 -0
- package/dist/{insight-hub → bugsnag}/client/api/base.js +39 -11
- package/dist/{insight-hub → bugsnag}/client/api/filters.js +2 -2
- package/dist/{insight-hub → bugsnag}/client.js +511 -29
- package/dist/common/info.js +1 -1
- package/dist/common/server.js +17 -5
- package/dist/index.js +11 -11
- package/dist/pactflow/client/ai.js +56 -6
- package/dist/pactflow/client/base.js +19 -1
- package/dist/pactflow/client/prompt-utils.js +89 -0
- package/dist/pactflow/client/prompts.js +133 -0
- package/dist/pactflow/client/tools.js +43 -2
- package/dist/pactflow/client/utils.js +70 -0
- package/dist/pactflow/client.js +192 -13
- package/package.json +9 -4
- package/dist/insight-hub/client/api/Project.js +0 -46
- package/dist/package.json +0 -60
- package/dist/tests/unit/common/server.test.js +0 -319
- package/dist/tests/unit/insight-hub/api-utilities.test.js +0 -31
- package/dist/tests/unit/insight-hub/client.test.js +0 -852
- package/dist/tests/unit/insight-hub/filters.test.js +0 -93
- package/dist/tests/unit/pactflow/ai.test.js +0 -21
- package/dist/tests/unit/pactflow/client.test.js +0 -67
- package/dist/tests/unit/pactflow/tools.test.js +0 -34
- package/dist/vitest.config.js +0 -57
- /package/dist/{insight-hub → bugsnag}/client/api/index.js +0 -0
- /package/dist/{insight-hub → bugsnag}/client/configuration.js +0 -0
- /package/dist/{insight-hub → bugsnag}/client/index.js +0 -0
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
<!-- Badges -->
|
|
11
11
|
<div>
|
|
12
|
-
<a href="https://github.com/SmartBear/smartbear-mcp/actions/workflows/
|
|
12
|
+
<a href="https://github.com/SmartBear/smartbear-mcp/actions/workflows/node-ci.yml"><img src="https://github.com/SmartBear/smartbear-mcp/actions/workflows/node-ci.yml/badge.svg?branch=next" alt="Test Status"></a>
|
|
13
13
|
<a href="https://smartbear.github.io/smartbear-mcp/"><img src="https://img.shields.io/badge/coverage-dynamic-brightgreen" alt="Coverage"></a>
|
|
14
14
|
<a href="https://www.npmjs.com/package/@smartbear/mcp"><img src="https://img.shields.io/npm/v/@smartbear/mcp" alt="npm version"></a>
|
|
15
15
|
<a href="https://modelcontextprotocol.io"><img src="https://img.shields.io/badge/MCP-Compatible-blue" alt="MCP Compatible"></a>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
</div>
|
|
19
19
|
<br />
|
|
20
20
|
|
|
21
|
-
A Model Context Protocol (MCP) server which provides AI assistants with seamless access to SmartBear's suite of testing and monitoring tools, including [
|
|
21
|
+
A Model Context Protocol (MCP) server which provides AI assistants with seamless access to SmartBear's suite of testing and monitoring tools, including [BugSnag](https://www.bugsnag.com/), [Reflect](https://reflect.run), [API Hub](https://www.smartbear.com/api-hub), [PactFlow](https://pactflow.io/) and [Pact Broker](https://docs.pact.io/)
|
|
22
22
|
|
|
23
23
|
## What is MCP?
|
|
24
24
|
|
|
@@ -28,7 +28,7 @@ The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction)
|
|
|
28
28
|
|
|
29
29
|
See individual guides for suggested prompts and supported tools and resources:
|
|
30
30
|
|
|
31
|
-
- [
|
|
31
|
+
- [BugSnag](https://developer.smartbear.com/smartbear-mcp/docs/bugsnag-integration) - Comprehensive error monitoring and debugging capabilities
|
|
32
32
|
- [Test Hub](https://developer.smartbear.com/smartbear-mcp/docs/test-hub-integration) - Test management and execution capabilities
|
|
33
33
|
- [API Hub](https://developer.smartbear.com/smartbear-mcp/docs/api-hub-integration) - Portal management capabilities
|
|
34
34
|
- [PactFlow](https://developer.smartbear.com/pactflow/default/getting-started) - Contract testing capabilities
|
|
@@ -37,14 +37,14 @@ See individual guides for suggested prompts and supported tools and resources:
|
|
|
37
37
|
## Prerequisites
|
|
38
38
|
|
|
39
39
|
- Node.js 20+ and npm
|
|
40
|
-
- Access to SmartBear products (
|
|
40
|
+
- Access to SmartBear products (BugSnag, Reflect, or API Hub)
|
|
41
41
|
- Valid API tokens for the products you want to integrate
|
|
42
42
|
|
|
43
43
|
## Installation
|
|
44
44
|
|
|
45
45
|
The MCP server is distributed as an npm package [`@smartbear/mcp`](https://www.npmjs.com/package/@smartbear/mcp), making it easy to integrate into your development workflow.
|
|
46
46
|
|
|
47
|
-
The server is started with the API key or auth token that you use with your SmartBear product(s). They are optional and can be removed from your configuration if you aren't using the product. For
|
|
47
|
+
The server is started with the API key or auth token that you use with your SmartBear product(s). They are optional and can be removed from your configuration if you aren't using the product. For BugSnag, if you provide a project API key it will narrow down all searches to a single project in your BugSnag dashboard. Leave this field blank if you wish to interact across multiple projects at a time.
|
|
48
48
|
|
|
49
49
|
### VS Code with Copilot
|
|
50
50
|
|
|
@@ -66,8 +66,8 @@ Alternatively, you can use `npx` (or globally install) the `@smartbear/mcp` pack
|
|
|
66
66
|
"@smartbear/mcp@latest"
|
|
67
67
|
],
|
|
68
68
|
"env": {
|
|
69
|
-
"
|
|
70
|
-
"
|
|
69
|
+
"BUGSNAG_AUTH_TOKEN": "${input:bugsnag_auth_token}",
|
|
70
|
+
"BUGSNAG_PROJECT_API_KEY": "${input:bugsnag_project_api_key}",
|
|
71
71
|
"REFLECT_API_TOKEN": "${input:reflect_api_token}",
|
|
72
72
|
"API_HUB_API_KEY": "${input:api_hub_api_key}",
|
|
73
73
|
"PACT_BROKER_BASE_URL": "${input:pact_broker_base_url}",
|
|
@@ -79,15 +79,15 @@ Alternatively, you can use `npx` (or globally install) the `@smartbear/mcp` pack
|
|
|
79
79
|
},
|
|
80
80
|
"inputs": [
|
|
81
81
|
{
|
|
82
|
-
"id": "
|
|
82
|
+
"id": "bugsnag_auth_token",
|
|
83
83
|
"type": "promptString",
|
|
84
|
-
"description": "
|
|
84
|
+
"description": "BugSnag Auth Token - leave blank to disable BugSnag tools",
|
|
85
85
|
"password": true
|
|
86
86
|
},
|
|
87
87
|
{
|
|
88
|
-
"id": "
|
|
88
|
+
"id": "bugsnag_project_api_key",
|
|
89
89
|
"type": "promptString",
|
|
90
|
-
"description": "
|
|
90
|
+
"description": "BugSnag Project API Key - for single project interactions",
|
|
91
91
|
"password": false
|
|
92
92
|
},
|
|
93
93
|
{
|
|
@@ -145,8 +145,8 @@ Add the following configuration to your `claude_desktop_config.json` to launch t
|
|
|
145
145
|
"@smartbear/mcp@latest"
|
|
146
146
|
],
|
|
147
147
|
"env": {
|
|
148
|
-
"
|
|
149
|
-
"
|
|
148
|
+
"BUGSNAG_AUTH_TOKEN": "your_personal_auth_token",
|
|
149
|
+
"BUGSNAG_PROJECT_API_KEY": "your_project_api_key",
|
|
150
150
|
"REFLECT_API_TOKEN": "your_reflect_token",
|
|
151
151
|
"API_HUB_API_KEY": "your_api_hub_key",
|
|
152
152
|
"PACT_BROKER_BASE_URL": "your_pactflow_or_pactbroker_base_url",
|
|
@@ -165,113 +165,7 @@ For detailed introduction, examples, and advanced configuration visit our 📖 [
|
|
|
165
165
|
|
|
166
166
|
## Local Development
|
|
167
167
|
|
|
168
|
-
For developers who want to contribute to the SmartBear MCP server,
|
|
169
|
-
|
|
170
|
-
1. **Clone the repository:**
|
|
171
|
-
```bash
|
|
172
|
-
git clone https://github.com/SmartBear/smartbear-mcp.git
|
|
173
|
-
cd smartbear-mcp
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
2. **Install dependencies:**
|
|
177
|
-
```bash
|
|
178
|
-
npm install
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
3. **Build the server:**
|
|
182
|
-
```bash
|
|
183
|
-
npm run build
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
4. **Add the server to your IDE** configuration updating `.vscode/mcp.json` (or equivalent) to point to your local build
|
|
187
|
-
|
|
188
|
-
<details>
|
|
189
|
-
<summary><strong>📋 VSCode (mcp.json)</strong></summary>
|
|
190
|
-
|
|
191
|
-
```json
|
|
192
|
-
{
|
|
193
|
-
"servers": {
|
|
194
|
-
"smartbear": {
|
|
195
|
-
"type": "stdio",
|
|
196
|
-
"command": "node",
|
|
197
|
-
"dev": { // <-- To allow debugging in VS Code
|
|
198
|
-
"watch": "dist/**/*.js",
|
|
199
|
-
"debug": {
|
|
200
|
-
"type": "node"
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
"args": ["<PATH_TO_SMARTBEAR_MCP_REPO>/dist/index.js"],
|
|
204
|
-
"env": {
|
|
205
|
-
"INSIGHT_HUB_AUTH_TOKEN": "${input:insight_hub_auth_token}",
|
|
206
|
-
"INSIGHT_HUB_PROJECT_API_KEY": "${input:insight_hub_project_api_key}",
|
|
207
|
-
"REFLECT_API_TOKEN": "${input:reflect_api_token}",
|
|
208
|
-
"API_HUB_API_KEY": "${input:api_hub_api_key}",
|
|
209
|
-
"PACT_BROKER_BASE_URL": "${input:pact_broker_base_url}",
|
|
210
|
-
"PACT_BROKER_TOKEN": "${input:pact_broker_token}",
|
|
211
|
-
"PACT_BROKER_USERNAME": "${input:pact_broker_username}",
|
|
212
|
-
"PACT_BROKER_PASSWORD": "${input:pact_broker_password}"
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
},
|
|
216
|
-
"inputs": [
|
|
217
|
-
{
|
|
218
|
-
"id": "insight_hub_auth_token",
|
|
219
|
-
"type": "promptString",
|
|
220
|
-
"description": "Insight Hub Auth Token - leave blank to disable Insight Hub tools",
|
|
221
|
-
"password": true
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
"id": "insight_hub_project_api_key",
|
|
225
|
-
"type": "promptString",
|
|
226
|
-
"description": "Insight Hub Project API Key - for single project interactions",
|
|
227
|
-
"password": false
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
"id": "reflect_api_token",
|
|
231
|
-
"type": "promptString",
|
|
232
|
-
"description": "Reflect API Token - leave blank to disable Reflect tools",
|
|
233
|
-
"password": true
|
|
234
|
-
},
|
|
235
|
-
{
|
|
236
|
-
"id": "api_hub_api_key",
|
|
237
|
-
"type": "promptString",
|
|
238
|
-
"description": "API Hub API Key - leave blank to disable API Hub tools",
|
|
239
|
-
"password": true
|
|
240
|
-
},
|
|
241
|
-
{
|
|
242
|
-
"id": "pact_broker_base_url",
|
|
243
|
-
"type": "promptString",
|
|
244
|
-
"description": "PactFlow or Pact Broker base url - leave blank to disable PactFlow tools",
|
|
245
|
-
"password": true
|
|
246
|
-
},
|
|
247
|
-
{
|
|
248
|
-
"id": "pact_broker_token",
|
|
249
|
-
"type": "promptString",
|
|
250
|
-
"description": "PactFlow Authentication Token",
|
|
251
|
-
"password": true
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
"id": "pact_broker_username",
|
|
255
|
-
"type": "promptString",
|
|
256
|
-
"description": "Pact Broker Username",
|
|
257
|
-
"password": true
|
|
258
|
-
},
|
|
259
|
-
{
|
|
260
|
-
"id": "pact_broker_password",
|
|
261
|
-
"type": "promptString",
|
|
262
|
-
"description": "Pact Broker Password",
|
|
263
|
-
"password": true
|
|
264
|
-
},
|
|
265
|
-
]
|
|
266
|
-
}
|
|
267
|
-
```
|
|
268
|
-
</details>
|
|
269
|
-
|
|
270
|
-
5. **Local testing** using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) web interface:
|
|
271
|
-
|
|
272
|
-
```bash
|
|
273
|
-
INSIGHT_HUB_AUTH_TOKEN="your_token" REFLECT_API_TOKEN="your_reflect_token" API_HUB_API_KEY="your_api_hub_key" PACT_BROKER_BASE_URL="your-pactflow-url" PACT_BROKER_TOKEN="your-pactflow-token" PACT_BROKER_USERNAME="your-pact-broker-username" PACT_BROKER_PASSWORD="your-pact-broker-password" npx @modelcontextprotocol/inspector npx @smartbear/mcp@latest
|
|
274
|
-
```
|
|
168
|
+
For developers who want to contribute to the SmartBear MCP server, please see the [CONTRIBUTING.md](CONTRIBUTING.md) guide.
|
|
275
169
|
|
|
276
170
|
## License
|
|
277
171
|
|
|
@@ -285,4 +179,4 @@ This MCP server is licensed under the MIT License. This means you are free to us
|
|
|
285
179
|
|
|
286
180
|
---
|
|
287
181
|
|
|
288
|
-
**SmartBear MCP Server** - Bringing the power of SmartBear's testing and monitoring ecosystem to your AI-powered development workflow.
|
|
182
|
+
**SmartBear MCP Server** - Bringing the power of SmartBear's testing and monitoring ecosystem to your AI-powered development workflow.
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { BaseAPI, pickFieldsFromArray } from './base.js';
|
|
2
2
|
// --- API Class ---
|
|
3
3
|
export class CurrentUserAPI extends BaseAPI {
|
|
4
|
+
static filterFields = ["collaborators_url", "projects_url", "upgrade_url"];
|
|
4
5
|
static organizationFields = ['id', 'name', 'slug'];
|
|
5
6
|
static projectFields = ['id', 'name', 'slug', 'api_key'];
|
|
6
7
|
constructor(configuration) {
|
|
7
|
-
super(configuration);
|
|
8
|
+
super(configuration, CurrentUserAPI.filterFields);
|
|
8
9
|
}
|
|
9
10
|
/**
|
|
10
11
|
* List the current user's organizations
|
|
@@ -38,7 +39,7 @@ export class CurrentUserAPI extends BaseAPI {
|
|
|
38
39
|
* @returns A promise that resolves to the list of projects in the organization
|
|
39
40
|
*/
|
|
40
41
|
async getOrganizationProjects(organizationId, options = {}) {
|
|
41
|
-
const {
|
|
42
|
+
const { ...queryOptions } = options;
|
|
42
43
|
const params = new URLSearchParams();
|
|
43
44
|
for (const [key, value] of Object.entries(queryOptions)) {
|
|
44
45
|
if (value !== undefined)
|
|
@@ -50,8 +51,7 @@ export class CurrentUserAPI extends BaseAPI {
|
|
|
50
51
|
const data = await this.request({
|
|
51
52
|
method: 'GET',
|
|
52
53
|
url,
|
|
53
|
-
},
|
|
54
|
-
// Only return allowed fields
|
|
54
|
+
}, true); // Always paginate for projects
|
|
55
55
|
return {
|
|
56
56
|
...data,
|
|
57
57
|
body: pickFieldsFromArray(data.body || [], CurrentUserAPI.projectFields)
|
|
@@ -22,8 +22,9 @@ export const ReopenConditions = [
|
|
|
22
22
|
];
|
|
23
23
|
// --- API Class ---
|
|
24
24
|
export class ErrorAPI extends BaseAPI {
|
|
25
|
+
static filterFields = ["url", "project_url", "events_url"];
|
|
25
26
|
constructor(configuration) {
|
|
26
|
-
super(configuration);
|
|
27
|
+
super(configuration, ErrorAPI.filterFields);
|
|
27
28
|
}
|
|
28
29
|
/**
|
|
29
30
|
* View an Error on a Project
|
|
@@ -95,9 +96,41 @@ export class ErrorAPI extends BaseAPI {
|
|
|
95
96
|
* GET /projects/{project_id}/errors
|
|
96
97
|
*/
|
|
97
98
|
async listProjectErrors(projectId, options = {}) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
let url = `/projects/${projectId}/errors`;
|
|
100
|
+
// Next links need to be used as-is to ensure results are consistent, so only the page size can be modified
|
|
101
|
+
if (options.next !== undefined) {
|
|
102
|
+
const nextUrl = new URL(options.next);
|
|
103
|
+
if (options.per_page !== undefined) {
|
|
104
|
+
nextUrl.searchParams.set('per_page', options.per_page.toString());
|
|
105
|
+
}
|
|
106
|
+
url = nextUrl.toString();
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const params = new URLSearchParams();
|
|
110
|
+
// Add filter parameters
|
|
111
|
+
if (options.filters) {
|
|
112
|
+
const filterParams = new URLSearchParams(toQueryString(options.filters));
|
|
113
|
+
filterParams.forEach((value, key) => {
|
|
114
|
+
params.append(key, value);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
// Add pagination and sorting parameters
|
|
118
|
+
if (options.base !== undefined) {
|
|
119
|
+
params.append('base', options.base);
|
|
120
|
+
}
|
|
121
|
+
if (options.sort !== undefined) {
|
|
122
|
+
params.append('sort', options.sort);
|
|
123
|
+
}
|
|
124
|
+
if (options.direction !== undefined) {
|
|
125
|
+
params.append('direction', options.direction);
|
|
126
|
+
}
|
|
127
|
+
if (options.per_page !== undefined) {
|
|
128
|
+
params.append('per_page', options.per_page.toString());
|
|
129
|
+
}
|
|
130
|
+
if (params.size > 0) {
|
|
131
|
+
url = `/projects/${projectId}/errors?${params}`;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
101
134
|
return (await this.request({
|
|
102
135
|
method: 'GET',
|
|
103
136
|
url,
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { BaseAPI, pickFieldsFromArray, pickFields } from "./base.js";
|
|
2
|
+
// --- API Class ---
|
|
3
|
+
export class ProjectAPI extends BaseAPI {
|
|
4
|
+
static filterFields = ["errors_url", "events_url", "url", "html_url"];
|
|
5
|
+
static eventFieldFields = ["custom", "display_id", "filter_options", "pivot_options"];
|
|
6
|
+
static buildFields = [
|
|
7
|
+
"id",
|
|
8
|
+
"release_time",
|
|
9
|
+
"app_version",
|
|
10
|
+
"release_stage",
|
|
11
|
+
"errors_introduced_count",
|
|
12
|
+
"errors_seen_count",
|
|
13
|
+
"total_sessions_count",
|
|
14
|
+
"unhandled_sessions_count",
|
|
15
|
+
"accumulative_daily_users_seen",
|
|
16
|
+
"accumulative_daily_users_with_unhandled",
|
|
17
|
+
];
|
|
18
|
+
static releaseFields = [
|
|
19
|
+
"id",
|
|
20
|
+
"release_stage_name",
|
|
21
|
+
"app_version",
|
|
22
|
+
"first_released_at",
|
|
23
|
+
"first_release_id",
|
|
24
|
+
"releases_count",
|
|
25
|
+
"visible",
|
|
26
|
+
"total_sessions_count",
|
|
27
|
+
"unhandled_sessions_count",
|
|
28
|
+
"sessions_count_in_last_24h",
|
|
29
|
+
"accumulative_daily_users_seen",
|
|
30
|
+
"accumulative_daily_users_with_unhandled",
|
|
31
|
+
];
|
|
32
|
+
static stabilityFields = [
|
|
33
|
+
"critical_stability",
|
|
34
|
+
"target_stability",
|
|
35
|
+
"stability_target_type",
|
|
36
|
+
];
|
|
37
|
+
constructor(configuration) {
|
|
38
|
+
super(configuration, ProjectAPI.filterFields);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* List the Event Fields for a Project
|
|
42
|
+
* GET /projects/{project_id}/event_fields
|
|
43
|
+
* @param projectId The project ID
|
|
44
|
+
* @returns A promise that resolves to the list of event fields
|
|
45
|
+
*/
|
|
46
|
+
async listProjectEventFields(projectId) {
|
|
47
|
+
const url = `/projects/${projectId}/event_fields`;
|
|
48
|
+
const data = await this.request({
|
|
49
|
+
method: 'GET',
|
|
50
|
+
url,
|
|
51
|
+
});
|
|
52
|
+
// Only return allowed fields
|
|
53
|
+
return {
|
|
54
|
+
...data,
|
|
55
|
+
body: pickFieldsFromArray(data.body || [], ProjectAPI.eventFieldFields)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Create a Project in an Organization
|
|
60
|
+
* POST /organizations/{organization_id}/projects
|
|
61
|
+
* @param organizationId The organization ID
|
|
62
|
+
* @param data The project creation request body
|
|
63
|
+
* @returns A promise that resolves to the created project
|
|
64
|
+
*/
|
|
65
|
+
async createProject(organizationId, data) {
|
|
66
|
+
const url = `/organizations/${organizationId}/projects`;
|
|
67
|
+
return await this.request({
|
|
68
|
+
method: 'POST',
|
|
69
|
+
url,
|
|
70
|
+
body: data,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Retrieves the stability targets for a specific project.
|
|
75
|
+
* GET /projects/{project_id} (with internal header)
|
|
76
|
+
* @param projectId The ID of the project.
|
|
77
|
+
* @returns A promise that resolves to the project's stability targets.
|
|
78
|
+
*/
|
|
79
|
+
async getProjectStabilityTargets(projectId) {
|
|
80
|
+
const url = `/projects/${projectId}`;
|
|
81
|
+
const response = await this.request({
|
|
82
|
+
method: "GET",
|
|
83
|
+
url,
|
|
84
|
+
});
|
|
85
|
+
return pickFields(response.body || {}, ProjectAPI.stabilityFields);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Lists builds for a specific project.
|
|
89
|
+
* GET /projects/{project_id}/releases
|
|
90
|
+
* @param projectId The ID of the project.
|
|
91
|
+
* @param opts Options for listing releases, including filtering by release stage.
|
|
92
|
+
* @returns A promise that resolves to an array of `ListReleasesResponse` objects.
|
|
93
|
+
*/
|
|
94
|
+
async listBuilds(projectId, opts) {
|
|
95
|
+
const url = opts.next_url ?? `/projects/${projectId}/releases${opts.release_stage ? `?release_stage=${opts.release_stage}` : ""}`;
|
|
96
|
+
const response = await this.request({
|
|
97
|
+
method: "GET",
|
|
98
|
+
url,
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
...response,
|
|
102
|
+
body: pickFieldsFromArray(response.body || [], ProjectAPI.buildFields),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Retrieves a specific build from a project.
|
|
107
|
+
* GET /projects/{project_id}/releases/{release_id}
|
|
108
|
+
* @param projectId The ID of the project.
|
|
109
|
+
* @param buildId The ID of the release to retrieve.
|
|
110
|
+
* @returns A promise that resolves to the release data.
|
|
111
|
+
*/
|
|
112
|
+
async getBuild(projectId, buildId) {
|
|
113
|
+
const url = `/projects/${projectId}/releases/${buildId}`;
|
|
114
|
+
return await this.request({
|
|
115
|
+
method: "GET",
|
|
116
|
+
url,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Lists releases for a specific project.
|
|
121
|
+
* GET /projects/{project_id}/release_groups
|
|
122
|
+
* @param projectId The ID of the project.
|
|
123
|
+
* @param opts Options for listing releases, including filtering by release stage and visibility.
|
|
124
|
+
* @returns A promise that resolves to an array of `ReleaseSummaryResponse` objects.
|
|
125
|
+
*/
|
|
126
|
+
async listReleases(projectId, opts) {
|
|
127
|
+
const url = opts.next_url ?? `/projects/${projectId}/release_groups?release_stage_name=${opts.release_stage_name}&visible_only=${opts.visible_only}&top_only=true`;
|
|
128
|
+
const response = await this.request({
|
|
129
|
+
method: "GET",
|
|
130
|
+
url
|
|
131
|
+
});
|
|
132
|
+
return {
|
|
133
|
+
...response,
|
|
134
|
+
body: pickFieldsFromArray(response.body || [], ProjectAPI.releaseFields),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Retrieves a specific release by its ID.
|
|
139
|
+
* GET /release_groups/{release_id}
|
|
140
|
+
* @param releaseId The ID of the release to retrieve.
|
|
141
|
+
* @returns A promise that resolves to the release data.
|
|
142
|
+
*/
|
|
143
|
+
async getRelease(releaseId) {
|
|
144
|
+
const url = `/release_groups/${releaseId}`;
|
|
145
|
+
return await this.request({
|
|
146
|
+
method: "GET",
|
|
147
|
+
url,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Lists builds associated with a specific release group.
|
|
152
|
+
* GET /release_groups/{release_id}/releases
|
|
153
|
+
* @param releaseId The ID of the release group.
|
|
154
|
+
* @return A promise that resolves to an array of `BuildResponse` objects.
|
|
155
|
+
*/
|
|
156
|
+
async listBuildsInRelease(releaseId) {
|
|
157
|
+
const url = `/release_groups/${releaseId}/releases`;
|
|
158
|
+
return await this.request({
|
|
159
|
+
method: "GET",
|
|
160
|
+
url,
|
|
161
|
+
}, true);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -12,10 +12,30 @@ export function pickFields(obj, keys) {
|
|
|
12
12
|
export function pickFieldsFromArray(arr, keys) {
|
|
13
13
|
return arr.map(obj => pickFields(obj, keys));
|
|
14
14
|
}
|
|
15
|
+
// Utility to extract next URL path from Link header
|
|
16
|
+
export function getNextUrlPathFromHeader(headers, basePath) {
|
|
17
|
+
if (!headers)
|
|
18
|
+
return null;
|
|
19
|
+
const link = headers.get("link") || headers.get("Link");
|
|
20
|
+
if (!link)
|
|
21
|
+
return null;
|
|
22
|
+
const match = link.match(/<([^>]+)>;\s*rel="next"/)?.[1];
|
|
23
|
+
if (!match)
|
|
24
|
+
return null;
|
|
25
|
+
return match.replace(basePath, "");
|
|
26
|
+
}
|
|
27
|
+
// Ensure URL is absolute
|
|
28
|
+
// The MCP tools exposed use only the path for pagination
|
|
29
|
+
// For making requests, we need to ensure the URL is absolute
|
|
30
|
+
export function ensureFullUrl(url, basePath) {
|
|
31
|
+
return url.startsWith('http') ? url : `${basePath}${url}`;
|
|
32
|
+
}
|
|
15
33
|
export class BaseAPI {
|
|
16
34
|
configuration;
|
|
17
|
-
|
|
35
|
+
filterFields;
|
|
36
|
+
constructor(configuration, filterFields) {
|
|
18
37
|
this.configuration = configuration;
|
|
38
|
+
this.filterFields = filterFields || [];
|
|
19
39
|
}
|
|
20
40
|
async request(options, paginate = false) {
|
|
21
41
|
const headers = {
|
|
@@ -28,11 +48,11 @@ export class BaseAPI {
|
|
|
28
48
|
headers,
|
|
29
49
|
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
30
50
|
};
|
|
31
|
-
const url = options.url.startsWith('http') ? options.url : `${this.configuration.basePath || ''}${options.url}`;
|
|
32
51
|
let results = [];
|
|
33
|
-
let nextUrl = url;
|
|
52
|
+
let nextUrl = options.url;
|
|
34
53
|
let apiResponse;
|
|
35
54
|
do {
|
|
55
|
+
nextUrl = ensureFullUrl(nextUrl, this.configuration.basePath);
|
|
36
56
|
const response = await fetch(nextUrl, fetchOptions);
|
|
37
57
|
if (!response.ok) {
|
|
38
58
|
const errorText = await response.text();
|
|
@@ -45,14 +65,7 @@ export class BaseAPI {
|
|
|
45
65
|
const data = await response.json();
|
|
46
66
|
if (paginate) {
|
|
47
67
|
results = results.concat(data);
|
|
48
|
-
|
|
49
|
-
if (link) {
|
|
50
|
-
const match = link.match(/<([^>]+)>;\s*rel="next"/);
|
|
51
|
-
nextUrl = match ? match[1] : undefined;
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
nextUrl = undefined;
|
|
55
|
-
}
|
|
68
|
+
nextUrl = getNextUrlPathFromHeader(response.headers, this.configuration.basePath);
|
|
56
69
|
}
|
|
57
70
|
else {
|
|
58
71
|
apiResponse.body = data;
|
|
@@ -61,6 +74,21 @@ export class BaseAPI {
|
|
|
61
74
|
if (paginate) {
|
|
62
75
|
apiResponse.body = results;
|
|
63
76
|
}
|
|
77
|
+
if (Array.isArray(apiResponse.body)) {
|
|
78
|
+
apiResponse.body.forEach(this.sanitizeResponse.bind(this));
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
this.sanitizeResponse(apiResponse.body ?? {});
|
|
82
|
+
}
|
|
64
83
|
return apiResponse;
|
|
65
84
|
}
|
|
85
|
+
sanitizeResponse(data) {
|
|
86
|
+
if (!data)
|
|
87
|
+
return;
|
|
88
|
+
for (const key of this.filterFields) {
|
|
89
|
+
if (key in data) {
|
|
90
|
+
delete data[key];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
66
94
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Filters utility for
|
|
2
|
+
* Filters utility for BugSnag API
|
|
3
3
|
*
|
|
4
4
|
* This file provides utility functions for creating filter URL parameters
|
|
5
|
-
* based on the
|
|
5
|
+
* based on the BugSnag filtering specification described in the Filtering.md document.
|
|
6
6
|
*/
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
export const FilterValueSchema = z.object({
|