@smartbear/mcp 0.5.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 +2 -108
- package/dist/bugsnag/client/api/CurrentUser.js +2 -3
- package/dist/bugsnag/client/api/Project.js +122 -6
- package/dist/bugsnag/client/api/base.js +21 -10
- package/dist/bugsnag/client.js +431 -17
- package/dist/common/server.js +9 -0
- package/dist/index.js +2 -2
- package/dist/pactflow/client/ai.js +33 -2
- package/dist/pactflow/client/base.js +16 -3
- package/dist/pactflow/client/prompt-utils.js +89 -0
- package/dist/pactflow/client/prompts.js +133 -0
- package/dist/pactflow/client/tools.js +35 -3
- package/dist/pactflow/client.js +137 -6
- package/package.json +4 -2
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>
|
|
@@ -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
|
-
"BUGSNAG_AUTH_TOKEN": "${input:bugsnag_auth_token}",
|
|
206
|
-
"BUGSNAG_PROJECT_API_KEY": "${input:bugsnag_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": "bugsnag_auth_token",
|
|
219
|
-
"type": "promptString",
|
|
220
|
-
"description": "BugSnag Auth Token - leave blank to disable BugSnag tools",
|
|
221
|
-
"password": true
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
"id": "bugsnag_project_api_key",
|
|
225
|
-
"type": "promptString",
|
|
226
|
-
"description": "BugSnag 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
|
-
BUGSNAG_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
|
|
|
@@ -39,7 +39,7 @@ export class CurrentUserAPI extends BaseAPI {
|
|
|
39
39
|
* @returns A promise that resolves to the list of projects in the organization
|
|
40
40
|
*/
|
|
41
41
|
async getOrganizationProjects(organizationId, options = {}) {
|
|
42
|
-
const {
|
|
42
|
+
const { ...queryOptions } = options;
|
|
43
43
|
const params = new URLSearchParams();
|
|
44
44
|
for (const [key, value] of Object.entries(queryOptions)) {
|
|
45
45
|
if (value !== undefined)
|
|
@@ -51,8 +51,7 @@ export class CurrentUserAPI extends BaseAPI {
|
|
|
51
51
|
const data = await this.request({
|
|
52
52
|
method: 'GET',
|
|
53
53
|
url,
|
|
54
|
-
},
|
|
55
|
-
// Only return allowed fields
|
|
54
|
+
}, true); // Always paginate for projects
|
|
56
55
|
return {
|
|
57
56
|
...data,
|
|
58
57
|
body: pickFieldsFromArray(data.body || [], CurrentUserAPI.projectFields)
|
|
@@ -1,12 +1,38 @@
|
|
|
1
|
-
import { BaseAPI, pickFieldsFromArray } from "./base.js";
|
|
1
|
+
import { BaseAPI, pickFieldsFromArray, pickFields } from "./base.js";
|
|
2
2
|
// --- API Class ---
|
|
3
3
|
export class ProjectAPI extends BaseAPI {
|
|
4
4
|
static filterFields = ["errors_url", "events_url", "url", "html_url"];
|
|
5
|
-
static eventFieldFields = [
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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",
|
|
10
36
|
];
|
|
11
37
|
constructor(configuration) {
|
|
12
38
|
super(configuration, ProjectAPI.filterFields);
|
|
@@ -44,4 +70,94 @@ export class ProjectAPI extends BaseAPI {
|
|
|
44
70
|
body: data,
|
|
45
71
|
});
|
|
46
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
|
+
}
|
|
47
163
|
}
|
|
@@ -12,6 +12,24 @@ 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;
|
|
@@ -30,11 +48,11 @@ export class BaseAPI {
|
|
|
30
48
|
headers,
|
|
31
49
|
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
32
50
|
};
|
|
33
|
-
const url = options.url.startsWith('http') ? options.url : `${this.configuration.basePath || ''}${options.url}`;
|
|
34
51
|
let results = [];
|
|
35
|
-
let nextUrl = url;
|
|
52
|
+
let nextUrl = options.url;
|
|
36
53
|
let apiResponse;
|
|
37
54
|
do {
|
|
55
|
+
nextUrl = ensureFullUrl(nextUrl, this.configuration.basePath);
|
|
38
56
|
const response = await fetch(nextUrl, fetchOptions);
|
|
39
57
|
if (!response.ok) {
|
|
40
58
|
const errorText = await response.text();
|
|
@@ -47,14 +65,7 @@ export class BaseAPI {
|
|
|
47
65
|
const data = await response.json();
|
|
48
66
|
if (paginate) {
|
|
49
67
|
results = results.concat(data);
|
|
50
|
-
|
|
51
|
-
if (link) {
|
|
52
|
-
const match = link.match(/<([^>]+)>;\s*rel="next"/);
|
|
53
|
-
nextUrl = match ? match[1] : undefined;
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
nextUrl = undefined;
|
|
57
|
-
}
|
|
68
|
+
nextUrl = getNextUrlPathFromHeader(response.headers, this.configuration.basePath);
|
|
58
69
|
}
|
|
59
70
|
else {
|
|
60
71
|
apiResponse.body = data;
|