@smartbear/mcp 0.1.0 → 0.2.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 +61 -20
- package/api-hub/README.md +29 -0
- package/assets/smartbear-logo-dark.svg +17 -0
- package/assets/smartbear-logo-light.svg +17 -0
- package/dist/api-hub/client.js +1 -1
- package/dist/index.js +2 -1
- package/dist/insight-hub/client/api/CurrentUser.js +1 -1
- package/dist/insight-hub/client/api/Error.js +14 -0
- package/dist/insight-hub/client/api/Project.js +28 -0
- package/dist/insight-hub/client/api/base.js +4 -0
- package/dist/insight-hub/client/api/filters.js +167 -0
- package/dist/insight-hub/client.js +153 -29
- package/dist/package.json +19 -4
- package/dist/reflect/client.js +2 -2
- package/insight-hub/README.md +39 -0
- package/package.json +19 -4
- package/reflect/README.md +25 -0
package/README.md
CHANGED
|
@@ -10,30 +10,26 @@
|
|
|
10
10
|
|
|
11
11
|
An [MCP](https://modelcontextprotocol.io) server for SmartBear's API Hub, Test Hub and Insight Hub.
|
|
12
12
|
|
|
13
|
-
## Build
|
|
14
|
-
|
|
15
|
-
Checkout this repository and run the following to build the server:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm run build
|
|
19
|
-
```
|
|
20
|
-
|
|
21
13
|
## Usage
|
|
22
14
|
|
|
23
15
|
The server is started with the API key or auth token that you use with your product(s). They are optional and can be removed from your configuration if you aren't using the product.
|
|
24
16
|
|
|
25
17
|
### VS Code
|
|
26
18
|
|
|
27
|
-
Add the
|
|
19
|
+
Add the [`@smartbear/mcp`](https://www.npmjs.com/package/@smartbear/mcp) package to your project via NPM or via the "MCP: Add server…" command in VS Code.
|
|
20
|
+
|
|
21
|
+
If setting up manually, add the following configuration to `.vscode/mcp.json`:
|
|
28
22
|
|
|
29
23
|
```json
|
|
30
24
|
{
|
|
31
25
|
"servers": {
|
|
32
26
|
"smartbear": {
|
|
33
27
|
"type": "stdio",
|
|
34
|
-
"command": "
|
|
35
|
-
"args": [
|
|
36
|
-
|
|
28
|
+
"command": "npx",
|
|
29
|
+
"args": [
|
|
30
|
+
"-y",
|
|
31
|
+
"@smartbear/mcp@latest"
|
|
32
|
+
],
|
|
37
33
|
"env": {
|
|
38
34
|
"INSIGHT_HUB_AUTH_TOKEN": "${input:insight_hub_auth_token}",
|
|
39
35
|
"REFLECT_API_TOKEN": "${input:reflect_api_token}",
|
|
@@ -66,14 +62,23 @@ Add the following configuration to `.vscode/mcp.json`, replacing `<PATH_TO_SMART
|
|
|
66
62
|
|
|
67
63
|
### MCP Inspector
|
|
68
64
|
|
|
69
|
-
To test the MCP server
|
|
65
|
+
To test the MCP server using the npm package, run:
|
|
70
66
|
|
|
71
67
|
```bash
|
|
72
|
-
REFLECT_API_TOKEN=your_reflect_token INSIGHT_HUB_AUTH_TOKEN=your_insight_hub_token API_HUB_API_KEY=your_api_hub_api_key npx @
|
|
68
|
+
REFLECT_API_TOKEN=your_reflect_token INSIGHT_HUB_AUTH_TOKEN=your_insight_hub_token API_HUB_API_KEY=your_api_hub_api_key npx @smartbear/mcp
|
|
73
69
|
```
|
|
74
70
|
|
|
75
71
|
This will open an inspector window in your browser, where you can test the tools.
|
|
76
72
|
|
|
73
|
+
## Supported Tools
|
|
74
|
+
|
|
75
|
+
See individual guides for suggested prompts and supported tools and resources:
|
|
76
|
+
|
|
77
|
+
- [Insight Hub](./insight-hub/README.md)\
|
|
78
|
+
Get your top events and invite your LLM to help you fix them.
|
|
79
|
+
- [Reflect](./reflect/README.md)
|
|
80
|
+
- [API Hub](./api-hub/README.md)
|
|
81
|
+
|
|
77
82
|
## Environment Variables
|
|
78
83
|
|
|
79
84
|
- `INSIGHT_HUB_AUTH_TOKEN`: Required for Insight Hub tools. The Auth Token for Insight Hub.
|
|
@@ -81,14 +86,50 @@ This will open an inspector window in your browser, where you can test the tools
|
|
|
81
86
|
- `API_HUB_API_KEY`: Required for API Hub tools. The API Key for API Hub tools.
|
|
82
87
|
- `MCP_SERVER_INSIGHT_HUB_API_KEY`: Optional. If set, enables error reporting of the _MCP_server_ code via the BugSnag SDK. This is useful for debugging and monitoring of the MCP server itself and shouldn't be set to the same API key as your app.
|
|
83
88
|
|
|
84
|
-
|
|
89
|
+
See individual guides for product-specific configuration via environment variables.
|
|
85
90
|
|
|
86
|
-
|
|
91
|
+
## Local Development
|
|
87
92
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
If you want to build and run the MCP server from source (for development or contribution):
|
|
94
|
+
|
|
95
|
+
### Build
|
|
96
|
+
|
|
97
|
+
Clone this repository and run:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npm install
|
|
101
|
+
npm run build
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Usage (Local Build)
|
|
105
|
+
|
|
106
|
+
Update your `.vscode/mcp.json` to point to your local build:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"servers": {
|
|
111
|
+
"smartbear": {
|
|
112
|
+
"type": "stdio",
|
|
113
|
+
"command": "node",
|
|
114
|
+
"args": ["<PATH_TO_SMARTBEAR_MCP>/dist/index.js"],
|
|
115
|
+
"env": {
|
|
116
|
+
"INSIGHT_HUB_AUTH_TOKEN": "${input:insight_hub_auth_token}",
|
|
117
|
+
"REFLECT_API_TOKEN": "${input:reflect_api_token}",
|
|
118
|
+
"API_HUB_API_KEY": "${input:api_hub_api_key}"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"inputs": [
|
|
123
|
+
// ...same as above...
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Or run the server directly:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
REFLECT_API_TOKEN=your_reflect_token INSIGHT_HUB_AUTH_TOKEN=your_insight_hub_token API_HUB_API_KEY=your_api_hub_api_key node dist/index.js
|
|
132
|
+
```
|
|
92
133
|
|
|
93
134
|
## License
|
|
94
135
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# API Hub
|
|
2
|
+
|
|
3
|
+
## Environment Variables
|
|
4
|
+
|
|
5
|
+
- `API_HUB_API_KEY`: Required. The API key for API Hub MCP tools.
|
|
6
|
+
- `MCP_SERVER_INSIGHT_HUB_API_KEY`: Optional. If set, enables error reporting of the _MCP_server_ code via the BugSnag SDK. This is useful for debugging and monitoring of the MCP server itself and shouldn't be set to the same API key as your app.
|
|
7
|
+
|
|
8
|
+
## Tools
|
|
9
|
+
|
|
10
|
+
1. `list_portals`
|
|
11
|
+
- Search for available portals within API Hub. Only portals where you have at least a designer role, either at the product level or organization level, are returned.
|
|
12
|
+
2. `create_portal`
|
|
13
|
+
- Create a new portal within API Hub.
|
|
14
|
+
3. `get_portal`
|
|
15
|
+
- Retrieve information about a specific portal.
|
|
16
|
+
4. `delete_portal`
|
|
17
|
+
- Delete a portal.
|
|
18
|
+
5. `update_portal`
|
|
19
|
+
- Update a specific portal's configuration.
|
|
20
|
+
6. `list_portal_products`
|
|
21
|
+
- Get products for a specific portal matching your criteria.
|
|
22
|
+
7. `create_portal_product`
|
|
23
|
+
- Create a new product within a specific portal.
|
|
24
|
+
8. `get_portal_product`
|
|
25
|
+
- Retrieve information about a specific product resource.
|
|
26
|
+
9. `delete_portal_product`
|
|
27
|
+
- Delete a product from a specific portal.
|
|
28
|
+
10. `update_portal_product`
|
|
29
|
+
- Update a product's settings within a specific portal.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="253.9" height="179.21" viewBox="0 0 253.9 179.21">
|
|
2
|
+
<title>SB_Logo_Vertical_BLK_RGB</title>
|
|
3
|
+
<g>
|
|
4
|
+
<path d="M146,115.56H108l-6.75,20.79h51.55Zm102,20.79-20-61.43L241,35.24,193,0.4,159.26,24.94H94.7L60.93,0.4,13,35.24,25.87,74.92l-20,61.43h63L64,121.28l63-45.76,63,45.76-4.89,15.07h63ZM108,115.56l-6.75,20.79h51.55L146,115.56H108Zm38.05,0H108l-6.75,20.79h51.55Z" transform="translate(-1.05 -0.4)" fill="#2c282c"/>
|
|
5
|
+
<g>
|
|
6
|
+
<path d="M17.86,156.7s-5.4-.62-7.64-0.62c-3.24,0-4.9,1.16-4.9,3.43,0,2.47,1.39,3,5.9,4.28,5.55,1.54,7.56,3,7.56,7.52,0,5.78-3.62,8.29-8.83,8.29a56.2,56.2,0,0,1-8.64-1l0.42-3.43s5.21,0.69,7.94.69c3.28,0,4.78-1.47,4.78-4.24,0-2.24-1.2-3-5.24-4C3.52,166.14,1,164.41,1,159.82c0-5.32,3.51-7.52,8.79-7.52a58.55,58.55,0,0,1,8.37.92Z" transform="translate(-1.05 -0.4)" fill="#2c282c"/>
|
|
7
|
+
<path d="M29.06,152.76H36.5l5.9,20.63,5.9-20.63h7.48v26.42H51.46V156.35H51.24l-6.6,21.75H40.16l-6.6-21.75H33.34v22.83H29.06V152.76Z" transform="translate(-1.05 -0.4)" fill="#2c282c"/>
|
|
8
|
+
<path d="M122.08,152.76h19.28v3.82h-7.44v22.6H129.6v-22.6h-7.52v-3.82Z" transform="translate(-1.05 -0.4)" fill="#2c282c"/>
|
|
9
|
+
<path d="M160,152.76c5.24,0,8.06,2,8.06,6.86,0,3.2-1,4.78-3.12,5.94,2.28,0.89,3.78,2.51,3.78,6.13,0,5.4-3.28,7.48-8.41,7.48H150V152.76h10Zm-5.75,3.7V164h5.67c2.62,0,3.78-1.31,3.78-3.9s-1.31-3.66-3.93-3.66h-5.52Zm0,11.18v7.83h5.82c2.66,0,4.24-.85,4.24-4,0-3-2.24-3.82-4.32-3.82h-5.75Z" transform="translate(-1.05 -0.4)" fill="#2c282c"/>
|
|
10
|
+
<path d="M179.32,152.76H196v3.74H183.6V164h10.07v3.66H183.6v7.75H196v3.78H179.32V152.76Z" transform="translate(-1.05 -0.4)" fill="#2c282c"/>
|
|
11
|
+
<path d="M211.33,152.76h8.52l6.56,26.42h-4.32l-2-7.75H211l-2,7.75h-4.28Zm0.57,14.89h7.41l-2.73-11.3h-1.93Z" transform="translate(-1.05 -0.4)" fill="#2c282c"/>
|
|
12
|
+
<path d="M240.22,171.35v7.83h-4.28V152.76h10c5.82,0,8.95,3.34,8.95,9.2,0,3.74-1.5,7.12-4.32,8.43l4.36,8.79h-4.71l-3.82-7.83h-6.21ZM246,156.46h-5.75v11.18H246c3.24,0,4.47-2.91,4.47-5.65C250.51,159,249,156.46,246,156.46Z" transform="translate(-1.05 -0.4)" fill="#2c282c"/>
|
|
13
|
+
<path d="M71.64,152.76h8.52l6.56,26.42H82.39l-2-7.75h-9.1l-2,7.75H65.12Zm0.57,14.89h7.41l-2.73-11.3H75Z" transform="translate(-1.05 -0.4)" fill="#2c282c"/>
|
|
14
|
+
<path d="M100.52,171.35v7.83H96.24V152.76h10c5.82,0,8.95,3.34,8.95,9.2,0,3.74-1.5,7.12-4.32,8.43l4.36,8.79h-4.71l-3.82-7.83h-6.21Zm5.75-14.89h-5.75v11.18h5.82c3.24,0,4.47-2.91,4.47-5.65C110.82,159,109.35,156.46,106.27,156.46Z" transform="translate(-1.05 -0.4)" fill="#2c282c"/>
|
|
15
|
+
</g>
|
|
16
|
+
</g>
|
|
17
|
+
</svg>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="253.9" height="179.21" viewBox="0 0 253.9 179.21">
|
|
2
|
+
<title>SB_Logo_Vertical_WT_RGB</title>
|
|
3
|
+
<g>
|
|
4
|
+
<path d="M146,115.56H108l-6.75,20.79h51.55Zm102,20.79-20-61.43L241,35.24,193,0.4,159.26,24.94H94.7L60.93,0.4,13,35.24,25.87,74.92l-20,61.43h63L64,121.28l63-45.76,63,45.76-4.89,15.07h63ZM108,115.56l-6.75,20.79h51.55L146,115.56H108Zm38.05,0H108l-6.75,20.79h51.55Z" transform="translate(-1.05 -0.4)" fill="#fff"/>
|
|
5
|
+
<g>
|
|
6
|
+
<path d="M17.86,156.7s-5.4-.62-7.64-0.62c-3.24,0-4.9,1.16-4.9,3.43,0,2.47,1.39,3,5.9,4.28,5.55,1.54,7.56,3,7.56,7.52,0,5.78-3.62,8.29-8.83,8.29a56.2,56.2,0,0,1-8.64-1l0.42-3.43s5.21,0.69,7.94.69c3.28,0,4.78-1.47,4.78-4.24,0-2.24-1.2-3-5.24-4C3.52,166.14,1,164.41,1,159.82c0-5.32,3.51-7.52,8.79-7.52a58.55,58.55,0,0,1,8.37.92Z" transform="translate(-1.05 -0.4)" fill="#fff"/>
|
|
7
|
+
<path d="M29.06,152.76H36.5l5.9,20.63,5.9-20.63h7.48v26.42H51.46V156.35H51.24l-6.6,21.75H40.16l-6.6-21.75H33.34v22.83H29.06V152.76Z" transform="translate(-1.05 -0.4)" fill="#fff"/>
|
|
8
|
+
<path d="M122.08,152.76h19.28v3.82h-7.44v22.6H129.6v-22.6h-7.52v-3.82Z" transform="translate(-1.05 -0.4)" fill="#fff"/>
|
|
9
|
+
<path d="M160,152.76c5.24,0,8.06,2,8.06,6.86,0,3.2-1,4.78-3.12,5.94,2.28,0.89,3.78,2.51,3.78,6.13,0,5.4-3.28,7.48-8.41,7.48H150V152.76h10Zm-5.75,3.7V164h5.67c2.62,0,3.78-1.31,3.78-3.9s-1.31-3.66-3.93-3.66h-5.52Zm0,11.18v7.83h5.82c2.66,0,4.24-.85,4.24-4,0-3-2.24-3.82-4.32-3.82h-5.75Z" transform="translate(-1.05 -0.4)" fill="#fff"/>
|
|
10
|
+
<path d="M179.32,152.76H196v3.74H183.6V164h10.07v3.66H183.6v7.75H196v3.78H179.32V152.76Z" transform="translate(-1.05 -0.4)" fill="#fff"/>
|
|
11
|
+
<path d="M211.33,152.76h8.52l6.56,26.42h-4.32l-2-7.75H211l-2,7.75h-4.28Zm0.57,14.89h7.41l-2.73-11.3h-1.93Z" transform="translate(-1.05 -0.4)" fill="#fff"/>
|
|
12
|
+
<path d="M240.22,171.35v7.83h-4.28V152.76h10c5.82,0,8.95,3.34,8.95,9.2,0,3.74-1.5,7.12-4.32,8.43l4.36,8.79h-4.71l-3.82-7.83h-6.21ZM246,156.46h-5.75v11.18H246c3.24,0,4.47-2.91,4.47-5.65C250.51,159,249,156.46,246,156.46Z" transform="translate(-1.05 -0.4)" fill="#fff"/>
|
|
13
|
+
<path d="M71.64,152.76h8.52l6.56,26.42H82.39l-2-7.75h-9.1l-2,7.75H65.12Zm0.57,14.89h7.41l-2.73-11.3H75Z" transform="translate(-1.05 -0.4)" fill="#fff"/>
|
|
14
|
+
<path d="M100.52,171.35v7.83H96.24V152.76h10c5.82,0,8.95,3.34,8.95,9.2,0,3.74-1.5,7.12-4.32,8.43l4.36,8.79h-4.71l-3.82-7.83h-6.21Zm5.75-14.89h-5.75v11.18h5.82c3.24,0,4.47-2.91,4.47-5.65C110.82,159,109.35,156.46,106.27,156.46Z" transform="translate(-1.05 -0.4)" fill="#fff"/>
|
|
15
|
+
</g>
|
|
16
|
+
</g>
|
|
17
|
+
</svg>
|
package/dist/api-hub/client.js
CHANGED
|
@@ -114,7 +114,7 @@ export class ApiHubClient {
|
|
|
114
114
|
content: [{ type: "text", text: "Portal deleted successfully." }],
|
|
115
115
|
};
|
|
116
116
|
});
|
|
117
|
-
server.tool("update_portal", "Update a
|
|
117
|
+
server.tool("update_portal", "Update a specific portal's configuration", {
|
|
118
118
|
portalId: z.string().describe("Portal UUID or subdomain."),
|
|
119
119
|
name: z.string().optional().describe("The portal name."),
|
|
120
120
|
subdomain: z.string().optional().describe("The portal subdomain."),
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,8 @@ async function main() {
|
|
|
35
35
|
reflectClient.registerResources(server);
|
|
36
36
|
}
|
|
37
37
|
if (insightHubToken) {
|
|
38
|
-
const insightHubClient = new InsightHubClient(insightHubToken);
|
|
38
|
+
const insightHubClient = new InsightHubClient(insightHubToken, process.env.INSIGHT_HUB_PROJECT_API_KEY, process.env.INSIGHT_HUB_ENDPOINT);
|
|
39
|
+
await insightHubClient.initialize();
|
|
39
40
|
insightHubClient.registerTools(server);
|
|
40
41
|
insightHubClient.registerResources(server);
|
|
41
42
|
}
|
|
@@ -2,7 +2,7 @@ import { BaseAPI, pickFieldsFromArray } from './base.js';
|
|
|
2
2
|
// --- API Class ---
|
|
3
3
|
export class CurrentUserAPI extends BaseAPI {
|
|
4
4
|
static organizationFields = ['id', 'name'];
|
|
5
|
-
static projectFields = ['id', 'name', 'slug'];
|
|
5
|
+
static projectFields = ['id', 'name', 'slug', 'api_key'];
|
|
6
6
|
constructor(configuration) {
|
|
7
7
|
super(configuration);
|
|
8
8
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BaseAPI } from './base.js';
|
|
2
|
+
import { toQueryString } from './filters.js';
|
|
2
3
|
// --- API Class ---
|
|
3
4
|
export class ErrorAPI extends BaseAPI {
|
|
4
5
|
constructor(configuration) {
|
|
@@ -58,4 +59,17 @@ export class ErrorAPI extends BaseAPI {
|
|
|
58
59
|
url,
|
|
59
60
|
}));
|
|
60
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* List the Errors on a Project
|
|
64
|
+
* GET /projects/{project_id}/errors
|
|
65
|
+
*/
|
|
66
|
+
async listProjectErrors(projectId, options = {}) {
|
|
67
|
+
const url = options.filters
|
|
68
|
+
? `/projects/${projectId}/errors?${toQueryString(options.filters)}`
|
|
69
|
+
: `/projects/${projectId}/errors`;
|
|
70
|
+
return (await this.request({
|
|
71
|
+
method: 'GET',
|
|
72
|
+
url,
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
61
75
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { BaseAPI, pickFieldsFromArray } from "./base.js";
|
|
2
|
+
// --- API Class ---
|
|
3
|
+
export class ProjectAPI extends BaseAPI {
|
|
4
|
+
static eventFieldFields = [
|
|
5
|
+
'custom',
|
|
6
|
+
'display_id',
|
|
7
|
+
'filter_options',
|
|
8
|
+
'pivot_options'
|
|
9
|
+
];
|
|
10
|
+
constructor(configuration) {
|
|
11
|
+
super(configuration);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* List the Event Fields for a Project
|
|
15
|
+
* GET /projects/{project_id}/event_fields
|
|
16
|
+
* @param projectId The project ID
|
|
17
|
+
* @returns A promise that resolves to the list of event fields
|
|
18
|
+
*/
|
|
19
|
+
async listProjectEventFields(projectId) {
|
|
20
|
+
const url = `/projects/${projectId}/event_fields`;
|
|
21
|
+
const data = await this.request({
|
|
22
|
+
method: 'GET',
|
|
23
|
+
url,
|
|
24
|
+
});
|
|
25
|
+
// Only return allowed fields
|
|
26
|
+
return pickFieldsFromArray(data, ProjectAPI.eventFieldFields);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -33,6 +33,10 @@ export class BaseAPI {
|
|
|
33
33
|
let nextUrl = url;
|
|
34
34
|
do {
|
|
35
35
|
const response = await fetch(nextUrl, fetchOptions);
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
const errorText = await response.text();
|
|
38
|
+
throw new Error(`Request failed with status ${response.status}: ${errorText}`);
|
|
39
|
+
}
|
|
36
40
|
const data = await response.json();
|
|
37
41
|
if (paginate) {
|
|
38
42
|
results = results.concat(data);
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filters utility for Insight Hub API
|
|
3
|
+
*
|
|
4
|
+
* This file provides utility functions for creating filter URL parameters
|
|
5
|
+
* based on the Insight Hub filtering specification described in the Filtering.md document.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
export const FilterValueSchema = z.object({
|
|
9
|
+
type: z.enum(['eq', 'ne', 'empty']),
|
|
10
|
+
value: z.union([z.string(), z.boolean(), z.number()]),
|
|
11
|
+
});
|
|
12
|
+
export const FilterObjectSchema = z.record(z.array(FilterValueSchema));
|
|
13
|
+
/**
|
|
14
|
+
* Creates a filter value object for equality comparison
|
|
15
|
+
*
|
|
16
|
+
* @param value The value to compare against
|
|
17
|
+
* @returns FilterValue with type 'eq'
|
|
18
|
+
*/
|
|
19
|
+
export function equals(value) {
|
|
20
|
+
return {
|
|
21
|
+
type: 'eq',
|
|
22
|
+
value,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Creates a filter value object for inequality comparison
|
|
27
|
+
*
|
|
28
|
+
* @param value The value to compare against
|
|
29
|
+
* @returns FilterValue with type 'ne'
|
|
30
|
+
*/
|
|
31
|
+
export function notEquals(value) {
|
|
32
|
+
return {
|
|
33
|
+
type: 'ne',
|
|
34
|
+
value,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Creates a filter value object for checking if a field is empty or not
|
|
39
|
+
*
|
|
40
|
+
* @param isEmpty Whether the field should be empty (true) or not (false)
|
|
41
|
+
* @returns FilterValue with type 'empty'
|
|
42
|
+
*/
|
|
43
|
+
export function empty(isEmpty) {
|
|
44
|
+
return {
|
|
45
|
+
type: 'empty',
|
|
46
|
+
value: isEmpty.toString(),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Creates a relative time filter for event.since or event.before
|
|
51
|
+
*
|
|
52
|
+
* @param value The amount of time
|
|
53
|
+
* @param unit The time unit ('h' for hours, 'd' for days)
|
|
54
|
+
* @returns FilterValue for the relative time
|
|
55
|
+
*/
|
|
56
|
+
export function relativeTime(value, unit) {
|
|
57
|
+
return {
|
|
58
|
+
type: 'eq',
|
|
59
|
+
value: `${value}${unit}`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Creates an ISO 8601 time filter (must be in UTC format like 2018-05-20T00:00:00Z)
|
|
64
|
+
*
|
|
65
|
+
* @param date The date object to convert to ISO string
|
|
66
|
+
* @returns FilterValue for the ISO time
|
|
67
|
+
*/
|
|
68
|
+
export function isoTime(date) {
|
|
69
|
+
return {
|
|
70
|
+
type: 'eq',
|
|
71
|
+
value: date.toISOString(),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Converts a FilterObject to URL search parameters
|
|
76
|
+
*
|
|
77
|
+
* @param filters The filter object to convert
|
|
78
|
+
* @returns URLSearchParams object with the encoded filters
|
|
79
|
+
*/
|
|
80
|
+
export function toUrlSearchParams(filters) {
|
|
81
|
+
const params = new URLSearchParams();
|
|
82
|
+
Object.entries(filters).forEach(([field, filterValues]) => {
|
|
83
|
+
filterValues.forEach((filterValue) => {
|
|
84
|
+
params.append(`filters[${field}][][type]`, filterValue.type);
|
|
85
|
+
params.append(`filters[${field}][][value]`, filterValue.value.toString());
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
return params;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Converts a FilterObject to a query string
|
|
92
|
+
*
|
|
93
|
+
* @param filters The filter object to convert
|
|
94
|
+
* @returns Query string representation of the filters
|
|
95
|
+
*/
|
|
96
|
+
export function toQueryString(filters) {
|
|
97
|
+
return toUrlSearchParams(filters).toString();
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Helper to build a FilterObject with type safety
|
|
101
|
+
*
|
|
102
|
+
* @returns An empty FilterObject that can be built upon
|
|
103
|
+
*/
|
|
104
|
+
export function createFilter() {
|
|
105
|
+
return {};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Adds a field filter to an existing FilterObject
|
|
109
|
+
*
|
|
110
|
+
* @param filters The FilterObject to add to
|
|
111
|
+
* @param field The field name (e.g., 'error.status', 'event.since')
|
|
112
|
+
* @param filterValue The FilterValue to add
|
|
113
|
+
* @returns The updated FilterObject for chaining
|
|
114
|
+
*/
|
|
115
|
+
export function addFilter(filters, field, filterValue) {
|
|
116
|
+
if (!filters[field]) {
|
|
117
|
+
filters[field] = [];
|
|
118
|
+
}
|
|
119
|
+
filters[field].push(filterValue);
|
|
120
|
+
return filters;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Utility to create a time range filter between two dates
|
|
124
|
+
*
|
|
125
|
+
* @param filters The FilterObject to add to
|
|
126
|
+
* @param since Start date
|
|
127
|
+
* @param before End date
|
|
128
|
+
* @returns The updated FilterObject for chaining
|
|
129
|
+
*/
|
|
130
|
+
export function addTimeRange(filters, since, before) {
|
|
131
|
+
addFilter(filters, 'event.since', isoTime(since));
|
|
132
|
+
addFilter(filters, 'event.before', isoTime(before));
|
|
133
|
+
return filters;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Utility to create a relative time range filter
|
|
137
|
+
*
|
|
138
|
+
* @param filters The FilterObject to add to
|
|
139
|
+
* @param amount The amount of time (e.g., 7 for 7 days)
|
|
140
|
+
* @param unit The time unit ('h' for hours, 'd' for days)
|
|
141
|
+
* @returns The updated FilterObject for chaining
|
|
142
|
+
*/
|
|
143
|
+
export function addRelativeTimeRange(filters, amount, unit) {
|
|
144
|
+
addFilter(filters, 'event.since', relativeTime(amount, unit));
|
|
145
|
+
return filters;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Usage examples:
|
|
149
|
+
*
|
|
150
|
+
* // Example 1: Open errors with events in the last day
|
|
151
|
+
* const filters = createFilter();
|
|
152
|
+
* addRelativeTimeRange(filters, 1, 'd');
|
|
153
|
+
* addFilter(filters, 'error.status', equals('open'));
|
|
154
|
+
* const queryString = toQueryString(filters);
|
|
155
|
+
*
|
|
156
|
+
* // Example 2: Events affecting specific users on a specific day
|
|
157
|
+
* const filters = createFilter();
|
|
158
|
+
* addTimeRange(filters, new Date('2017-01-01'), new Date('2017-01-02'));
|
|
159
|
+
* addFilter(filters, 'user.email', equals('user1@example.com'));
|
|
160
|
+
* addFilter(filters, 'user.email', equals('user2@example.com'));
|
|
161
|
+
* const queryString = toQueryString(filters);
|
|
162
|
+
*
|
|
163
|
+
* // Example 3: Events that have user data
|
|
164
|
+
* const filters = createFilter();
|
|
165
|
+
* addFilter(filters, 'user.id', empty(false));
|
|
166
|
+
* const queryString = toQueryString(filters);
|
|
167
|
+
*/
|
|
@@ -3,20 +3,71 @@ import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info.js";
|
|
|
3
3
|
import { CurrentUserAPI, ErrorAPI, Configuration } from "./client/index.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import Bugsnag from "../common/bugsnag.js";
|
|
6
|
+
import NodeCache from "node-cache";
|
|
7
|
+
import { ProjectAPI } from "./client/api/Project.js";
|
|
8
|
+
import { FilterObjectSchema } from "./client/api/filters.js";
|
|
9
|
+
const HUB_PREFIX = "00000";
|
|
10
|
+
const HUB_API_ENDPOINT = "https://api.insighthub.smartbear.com";
|
|
11
|
+
const DEFAULT_API_ENDPOINT = "https://api.bugsnag.com";
|
|
12
|
+
const cacheKeys = {
|
|
13
|
+
ORG: "insight_hub_org",
|
|
14
|
+
PROJECTS: "insight_hub_projects",
|
|
15
|
+
CURRENT_PROJECT: "insight_hub_current_project",
|
|
16
|
+
PROJECT_EVENT_FILTERS: "insight_hub_project_event_filters",
|
|
17
|
+
};
|
|
6
18
|
export class InsightHubClient {
|
|
7
19
|
currentUserApi;
|
|
8
20
|
errorsApi;
|
|
9
|
-
|
|
21
|
+
cache;
|
|
22
|
+
projectApi;
|
|
23
|
+
projectApiKey;
|
|
24
|
+
constructor(token, projectApiKey, endpoint) {
|
|
25
|
+
if (!endpoint) {
|
|
26
|
+
if (projectApiKey && projectApiKey.startsWith(HUB_PREFIX)) {
|
|
27
|
+
endpoint = HUB_API_ENDPOINT;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
endpoint = DEFAULT_API_ENDPOINT;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
10
33
|
const config = new Configuration({
|
|
11
34
|
authToken: token,
|
|
12
35
|
headers: {
|
|
13
36
|
"User-Agent": `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`,
|
|
14
37
|
"Content-Type": "application/json",
|
|
15
38
|
},
|
|
16
|
-
basePath:
|
|
39
|
+
basePath: endpoint,
|
|
17
40
|
});
|
|
18
41
|
this.currentUserApi = new CurrentUserAPI(config);
|
|
19
42
|
this.errorsApi = new ErrorAPI(config);
|
|
43
|
+
this.cache = new NodeCache();
|
|
44
|
+
this.projectApi = new ProjectAPI(config);
|
|
45
|
+
this.projectApiKey = projectApiKey;
|
|
46
|
+
}
|
|
47
|
+
async initialize() {
|
|
48
|
+
const orgs = await this.listOrgs();
|
|
49
|
+
if (!orgs || orgs.length === 0) {
|
|
50
|
+
throw new Error("No organizations found for the current user.");
|
|
51
|
+
}
|
|
52
|
+
// We should only have one org
|
|
53
|
+
this.cache.set(cacheKeys.ORG, orgs[0]);
|
|
54
|
+
const projects = await this.listProjects(orgs[0].id);
|
|
55
|
+
this.cache.set(cacheKeys.PROJECTS, projects);
|
|
56
|
+
if (this.projectApiKey) {
|
|
57
|
+
const project = projects.find((project) => project.api_key === this.projectApiKey);
|
|
58
|
+
if (!project) {
|
|
59
|
+
throw new Error(`Project with API key ${this.projectApiKey} not found in organization ${orgs[0].name}.`);
|
|
60
|
+
}
|
|
61
|
+
this.cache.set(cacheKeys.CURRENT_PROJECT, project);
|
|
62
|
+
const projectFields = await this.listProjectEventFields(project.id);
|
|
63
|
+
if (!projectFields || projectFields.length === 0) {
|
|
64
|
+
throw new Error(`No event fields found for project ${project.name}.`);
|
|
65
|
+
}
|
|
66
|
+
this.cache.set(cacheKeys.PROJECT_EVENT_FILTERS, projectFields);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async listProjectEventFields(projectId) {
|
|
70
|
+
return this.projectApi.listProjectEventFields(projectId);
|
|
20
71
|
}
|
|
21
72
|
async listOrgs() {
|
|
22
73
|
return this.currentUserApi.listUserOrganizations();
|
|
@@ -28,6 +79,25 @@ export class InsightHubClient {
|
|
|
28
79
|
};
|
|
29
80
|
return this.currentUserApi.getOrganizationProjects(orgId, options);
|
|
30
81
|
}
|
|
82
|
+
// This method retrieves all projects for the organization stored in the cache.
|
|
83
|
+
// If no projects are found in the cache, it fetches them from the API and
|
|
84
|
+
// stores them in the cache for future use.
|
|
85
|
+
// It throws an error if no organizations are found in the cache.
|
|
86
|
+
async getProjects() {
|
|
87
|
+
let projects = this.cache.get(cacheKeys.PROJECTS);
|
|
88
|
+
if (!projects) {
|
|
89
|
+
const org = this.cache.get(cacheKeys.ORG);
|
|
90
|
+
if (!org) {
|
|
91
|
+
throw new Error("No organization found in cache.");
|
|
92
|
+
}
|
|
93
|
+
projects = await this.listProjects(org.id);
|
|
94
|
+
this.cache.set(cacheKeys.PROJECTS, projects);
|
|
95
|
+
}
|
|
96
|
+
if (!projects) {
|
|
97
|
+
throw new Error("No projects found.");
|
|
98
|
+
}
|
|
99
|
+
return projects;
|
|
100
|
+
}
|
|
31
101
|
async getErrorDetails(projectId, errorId) {
|
|
32
102
|
return this.errorsApi.viewErrorOnProject(projectId, errorId);
|
|
33
103
|
}
|
|
@@ -42,29 +112,46 @@ export class InsightHubClient {
|
|
|
42
112
|
const eventDetails = await Promise.all(projects.map((p) => this.getProjectEvent(p.id, eventId).catch(_e => null)));
|
|
43
113
|
return eventDetails.find(event => !!event);
|
|
44
114
|
}
|
|
115
|
+
async listProjectErrors(projectId, filters) {
|
|
116
|
+
return this.errorsApi.listProjectErrors(projectId, { filters });
|
|
117
|
+
}
|
|
45
118
|
registerTools(server) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
119
|
+
if (!this.projectApiKey) {
|
|
120
|
+
server.tool("list_insight_hub_projects", "List all projects in an organization.", {
|
|
121
|
+
page_size: z.number().optional().describe("Number of projects to return per page"),
|
|
122
|
+
page: z.number().optional().describe("Page number to return"),
|
|
123
|
+
}, async (args, _extra) => {
|
|
124
|
+
try {
|
|
125
|
+
let projects = await this.getProjects();
|
|
126
|
+
if (!projects || projects.length === 0) {
|
|
127
|
+
return {
|
|
128
|
+
content: [{ type: "text", text: "No projects found." }],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (args.page_size || args.page) {
|
|
132
|
+
const pageSize = args.page_size || 10;
|
|
133
|
+
const page = args.page || 1;
|
|
134
|
+
projects = projects.slice((page - 1) * pageSize, page * pageSize);
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
content: [{ type: "text", text: JSON.stringify(projects) }],
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
Bugsnag.notify(e);
|
|
142
|
+
throw e;
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
60
146
|
server.tool("get_insight_hub_error", "Get error details from a project", {
|
|
61
|
-
projectId: z.string().describe("ID of the project"),
|
|
62
147
|
errorId: z.string().describe("ID of the error to fetch"),
|
|
148
|
+
...(!this.projectApiKey ? { projectId: z.string().optional().describe("ID of the project") } : {}),
|
|
63
149
|
}, async (args, _extra) => {
|
|
64
150
|
try {
|
|
65
|
-
|
|
151
|
+
const projectId = typeof args.projectId === 'string' ? args.projectId : this.cache.get(cacheKeys.CURRENT_PROJECT)?.id;
|
|
152
|
+
if (!projectId || !args.errorId)
|
|
66
153
|
throw new Error("Both projectId and errorId arguments are required");
|
|
67
|
-
const response = await this.getErrorDetails(
|
|
154
|
+
const response = await this.getErrorDetails(projectId, args.errorId);
|
|
68
155
|
return {
|
|
69
156
|
content: [{ type: "text", text: JSON.stringify(response) }],
|
|
70
157
|
};
|
|
@@ -102,9 +189,14 @@ export class InsightHubClient {
|
|
|
102
189
|
if (!projectSlug || !eventId)
|
|
103
190
|
throw new Error("Both projectSlug and eventId must be present in the link");
|
|
104
191
|
// get the project id from list of projects
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
192
|
+
const projects = this.cache.get("insight_hub_projects");
|
|
193
|
+
if (!projects) {
|
|
194
|
+
throw new Error("No projects found in cache.");
|
|
195
|
+
}
|
|
196
|
+
const projectId = projects.find((p) => p.slug === projectSlug)?.id;
|
|
197
|
+
if (!projectId) {
|
|
198
|
+
throw new Error("Project with the specified slug not found.");
|
|
199
|
+
}
|
|
108
200
|
const response = await this.getProjectEvent(projectId, eventId);
|
|
109
201
|
return {
|
|
110
202
|
content: [{ type: "text", text: JSON.stringify(response) }],
|
|
@@ -115,15 +207,45 @@ export class InsightHubClient {
|
|
|
115
207
|
throw e;
|
|
116
208
|
}
|
|
117
209
|
});
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
210
|
+
// Dynamically infer the filters schema from cached project event fields
|
|
211
|
+
server.tool("list_insight_hub_project_errors", "List errors in the current project based on a set of event filters. Use this tool to find or list errors based on user-provided search filters. You can use the `get_project_event_filters` tool to find valid filters for the current project.", {
|
|
212
|
+
filters: FilterObjectSchema.optional().describe("Filters to apply to the error list. Valid filters for a project can be found in the `get_project_event_filters` tool."),
|
|
213
|
+
// Conditionally add projectId only when no project API key is configured
|
|
214
|
+
...(this.projectApiKey ? {} : {
|
|
215
|
+
projectId: z.string().describe("ID of the project"),
|
|
216
|
+
}),
|
|
217
|
+
}, async (args, _extra) => {
|
|
121
218
|
try {
|
|
219
|
+
const projectId = typeof args.projectId === 'string' ? args.projectId : this.cache.get(cacheKeys.CURRENT_PROJECT)?.id;
|
|
220
|
+
if (!projectId)
|
|
221
|
+
throw new Error("projectId argument is required");
|
|
222
|
+
// Optionally, validate filter keys against cached event fields
|
|
223
|
+
const eventFields = this.cache.get(cacheKeys.PROJECT_EVENT_FILTERS) || [];
|
|
224
|
+
if (args.filters) {
|
|
225
|
+
const validKeys = new Set(eventFields.map(f => f.display_id));
|
|
226
|
+
for (const key of Object.keys(args.filters)) {
|
|
227
|
+
if (!validKeys.has(key)) {
|
|
228
|
+
throw new Error(`Invalid filter key: ${key}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const response = await this.listProjectErrors(projectId, args.filters);
|
|
122
233
|
return {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
234
|
+
content: [{ type: "text", text: JSON.stringify(response) }],
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
catch (e) {
|
|
238
|
+
Bugsnag.notify(e);
|
|
239
|
+
throw e;
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
server.tool("get_project_event_filters", "Get the available event filters for the current project. Use this tool to find valid filters for the `list_insight_hub_project_errors` tool.", {}, async (_args, _extra) => {
|
|
243
|
+
try {
|
|
244
|
+
const eventFields = this.cache.get(cacheKeys.PROJECT_EVENT_FILTERS);
|
|
245
|
+
if (!eventFields)
|
|
246
|
+
throw new Error("No event filters found in cache.");
|
|
247
|
+
return {
|
|
248
|
+
content: [{ type: "text", text: JSON.stringify(eventFields) }],
|
|
127
249
|
};
|
|
128
250
|
}
|
|
129
251
|
catch (e) {
|
|
@@ -131,6 +253,8 @@ export class InsightHubClient {
|
|
|
131
253
|
throw e;
|
|
132
254
|
}
|
|
133
255
|
});
|
|
256
|
+
}
|
|
257
|
+
registerResources(server) {
|
|
134
258
|
server.resource("insight_hub_event", new ResourceTemplate("insighthub://event/{id}", { list: undefined }), async (uri, { id }) => {
|
|
135
259
|
try {
|
|
136
260
|
return {
|
package/dist/package.json
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartbear/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP server for interacting SmartBear Products",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"smartbear",
|
|
7
|
+
"mcp",
|
|
8
|
+
"insight-hub",
|
|
9
|
+
"reflect",
|
|
10
|
+
"api-hub"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://developer.smartbear.com/smartbear-mcp",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git@github.com:SmartBear/smartbear-mcp.git"
|
|
16
|
+
},
|
|
5
17
|
"license": "MIT",
|
|
6
18
|
"type": "module",
|
|
7
19
|
"bin": {
|
|
8
|
-
"mcp
|
|
20
|
+
"mcp": "dist/index.js"
|
|
9
21
|
},
|
|
10
22
|
"files": [
|
|
11
|
-
"dist"
|
|
23
|
+
"dist",
|
|
24
|
+
"assets",
|
|
25
|
+
"**/README.md"
|
|
12
26
|
],
|
|
13
27
|
"config": {
|
|
14
28
|
"mcpServerName": "SmartBear MCP Server"
|
|
@@ -21,7 +35,8 @@
|
|
|
21
35
|
},
|
|
22
36
|
"dependencies": {
|
|
23
37
|
"@bugsnag/js": "^8.2.0",
|
|
24
|
-
"@modelcontextprotocol/sdk": "1.12.1"
|
|
38
|
+
"@modelcontextprotocol/sdk": "1.12.1",
|
|
39
|
+
"node-cache": "^5.1.2"
|
|
25
40
|
},
|
|
26
41
|
"devDependencies": {
|
|
27
42
|
"@eslint/js": "^9.29.0",
|
package/dist/reflect/client.js
CHANGED
|
@@ -8,7 +8,7 @@ export class ReflectClient {
|
|
|
8
8
|
"Content-Type": "application/json",
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
async
|
|
11
|
+
async listReflectSuites() {
|
|
12
12
|
const response = await fetch("https://api.reflect.run/v1/suites", {
|
|
13
13
|
method: "GET",
|
|
14
14
|
headers: this.headers,
|
|
@@ -66,7 +66,7 @@ export class ReflectClient {
|
|
|
66
66
|
}
|
|
67
67
|
registerTools(server) {
|
|
68
68
|
server.tool("list_reflect_suites", "List all reflect suites", {}, async (_args, _extra) => {
|
|
69
|
-
const response = await this.
|
|
69
|
+
const response = await this.listReflectSuites();
|
|
70
70
|
return {
|
|
71
71
|
content: [{ type: "text", text: JSON.stringify(response) }],
|
|
72
72
|
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Insight Hub
|
|
2
|
+
|
|
3
|
+
Fetch details of your app crashes and errors from your [Insight Hub](https://www.smartbear.com/insight-hub) dashboard for your LLM to help you diagnose and fix.
|
|
4
|
+
|
|
5
|
+
To connect an MCP server, you will need to create a personal auth token from the user settings page on your Insight Hub dashboard. If you wish to interact with only one Insight Hub project, we also recommend setting `INSIGHT_HUB_PROJECT_API_KEY` to reduce the scope of the conversation.
|
|
6
|
+
|
|
7
|
+
## Example prompts
|
|
8
|
+
|
|
9
|
+
- "Help me fix this crash from Insight Hub: https://app.bugsnag.com/my-org/my-project/errors/1a2b3c4d5e6f7g8h9i0j1k2l?&event_id=1a2b3c4d5e6f7g8h9i0j1k2l"
|
|
10
|
+
- "What are my top events for the 'example' project in insight hub?"
|
|
11
|
+
|
|
12
|
+
## Environment Variables
|
|
13
|
+
|
|
14
|
+
- `INSIGHT_HUB_AUTH_TOKEN`: (Required) The auth token for your account from your Insight Hub dashboard, under **Personal auth tokens** in user settings.
|
|
15
|
+
- `INSIGHT_HUB_PROJECT_API_KEY`: (Optional) The API key for the Insight Hub project you wish to interact with. Use this to scope all operations to a single project.
|
|
16
|
+
- `INSIGHT_HUB_ENDPOINT`: (Optional) The API server to connect to. Use this for on-premise installations to point to your own endpoint (e.g. `https://your.api.server`).
|
|
17
|
+
|
|
18
|
+
## Tools
|
|
19
|
+
|
|
20
|
+
1. `list_insight_hub_projects`
|
|
21
|
+
- List all projects in an organization.
|
|
22
|
+
- Multi-project mode only.
|
|
23
|
+
2. `get_insight_hub_error`
|
|
24
|
+
- Get error details from a project.
|
|
25
|
+
3. `get_insight_hub_error_latest_event`
|
|
26
|
+
- Get the latest event for an error.
|
|
27
|
+
4. `get_insight_hub_event_details`
|
|
28
|
+
- Get details of a specific event.
|
|
29
|
+
5. `get_project_event_filters`
|
|
30
|
+
- List the filters available for a project.
|
|
31
|
+
6. `list_insight_hub_project_errors`
|
|
32
|
+
- List and filter the errors from a project.
|
|
33
|
+
|
|
34
|
+
## Resources
|
|
35
|
+
|
|
36
|
+
- **insight_hub_event**
|
|
37
|
+
- URI Template: `insighthub://event/{id}`
|
|
38
|
+
- Description: Fetches details for a specific event by its event ID. Returns the event details in JSON format.
|
|
39
|
+
- Example usage: Retrieve event details for debugging or analysis by referencing the event ID.
|
package/package.json
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartbear/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP server for interacting SmartBear Products",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"smartbear",
|
|
7
|
+
"mcp",
|
|
8
|
+
"insight-hub",
|
|
9
|
+
"reflect",
|
|
10
|
+
"api-hub"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://developer.smartbear.com/smartbear-mcp",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git@github.com:SmartBear/smartbear-mcp.git"
|
|
16
|
+
},
|
|
5
17
|
"license": "MIT",
|
|
6
18
|
"type": "module",
|
|
7
19
|
"bin": {
|
|
8
|
-
"mcp
|
|
20
|
+
"mcp": "dist/index.js"
|
|
9
21
|
},
|
|
10
22
|
"files": [
|
|
11
|
-
"dist"
|
|
23
|
+
"dist",
|
|
24
|
+
"assets",
|
|
25
|
+
"**/README.md"
|
|
12
26
|
],
|
|
13
27
|
"config": {
|
|
14
28
|
"mcpServerName": "SmartBear MCP Server"
|
|
@@ -21,7 +35,8 @@
|
|
|
21
35
|
},
|
|
22
36
|
"dependencies": {
|
|
23
37
|
"@bugsnag/js": "^8.2.0",
|
|
24
|
-
"@modelcontextprotocol/sdk": "1.12.1"
|
|
38
|
+
"@modelcontextprotocol/sdk": "1.12.1",
|
|
39
|
+
"node-cache": "^5.1.2"
|
|
25
40
|
},
|
|
26
41
|
"devDependencies": {
|
|
27
42
|
"@eslint/js": "^9.29.0",
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Reflect
|
|
2
|
+
|
|
3
|
+
## Environment Variables
|
|
4
|
+
|
|
5
|
+
- `REFLECT_API_TOKEN`: Required. The Reflect Account API Key.
|
|
6
|
+
- `MCP_SERVER_INSIGHT_HUB_API_KEY`: Optional. If set, enables error reporting of the _MCP_server_ code via the BugSnag SDK. This is useful for debugging and monitoring of the MCP server itself and shouldn't be set to the same API key as your app.
|
|
7
|
+
|
|
8
|
+
## Tools
|
|
9
|
+
|
|
10
|
+
1. `list_reflect_suites`
|
|
11
|
+
- List all Reflect suites for your account.
|
|
12
|
+
2. `list_reflect_suite_executions`
|
|
13
|
+
- List all executions for a given Reflect suite.
|
|
14
|
+
3. `reflect_suite_execution_status`
|
|
15
|
+
- Get the status of a Reflect suite execution.
|
|
16
|
+
4. `reflect_suite_execution`
|
|
17
|
+
- Execute a Reflect suite.
|
|
18
|
+
5. `cancel_reflect_suite_execution`
|
|
19
|
+
- Cancel a Reflect suite execution.
|
|
20
|
+
6. `list_reflect_tests`
|
|
21
|
+
- List all Reflect tests.
|
|
22
|
+
7. `run_reflect_test`
|
|
23
|
+
- Run a Reflect test.
|
|
24
|
+
8. `reflect_test_status`
|
|
25
|
+
- Get the status of a Reflect test execution.
|