@smartbear/mcp 0.1.1 → 0.2.1
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 +20 -13
- package/dist/common/templates.js +54 -0
- 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 +337 -34
- package/dist/package.json +3 -2
- package/insight-hub/README.md +16 -10
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -32,6 +32,7 @@ If setting up manually, add the following configuration to `.vscode/mcp.json`:
|
|
|
32
32
|
],
|
|
33
33
|
"env": {
|
|
34
34
|
"INSIGHT_HUB_AUTH_TOKEN": "${input:insight_hub_auth_token}",
|
|
35
|
+
"INSIGHT_HUB_PROJECT_API_KEY": "${input:insight_hub_project_api_key}",
|
|
35
36
|
"REFLECT_API_TOKEN": "${input:reflect_api_token}",
|
|
36
37
|
"API_HUB_API_KEY": "${input:api_hub_api_key}"
|
|
37
38
|
}
|
|
@@ -41,19 +42,25 @@ If setting up manually, add the following configuration to `.vscode/mcp.json`:
|
|
|
41
42
|
{
|
|
42
43
|
"id": "insight_hub_auth_token",
|
|
43
44
|
"type": "promptString",
|
|
44
|
-
"description": "Insight Hub Auth Token",
|
|
45
|
+
"description": "Insight Hub Auth Token - leave blank to disable Insight Hub tools",
|
|
45
46
|
"password": true
|
|
46
47
|
},
|
|
48
|
+
{
|
|
49
|
+
"id": "insight_hub_project_api_key",
|
|
50
|
+
"type": "promptString",
|
|
51
|
+
"description": "Insight Hub Project API Key - for single project interactions",
|
|
52
|
+
"password": false
|
|
53
|
+
},
|
|
47
54
|
{
|
|
48
55
|
"id": "reflect_api_token",
|
|
49
56
|
"type": "promptString",
|
|
50
|
-
"description": "Reflect API Token",
|
|
57
|
+
"description": "Reflect API Token - leave blank to disable Reflect tools",
|
|
51
58
|
"password": true
|
|
52
59
|
},
|
|
53
60
|
{
|
|
54
61
|
"id": "api_hub_api_key",
|
|
55
62
|
"type": "promptString",
|
|
56
|
-
"description": "API Hub API Key",
|
|
63
|
+
"description": "API Hub API Key - leave blank to disable API Hub tools",
|
|
57
64
|
"password": true
|
|
58
65
|
}
|
|
59
66
|
]
|
|
@@ -70,13 +77,6 @@ REFLECT_API_TOKEN=your_reflect_token INSIGHT_HUB_AUTH_TOKEN=your_insight_hub_tok
|
|
|
70
77
|
|
|
71
78
|
This will open an inspector window in your browser, where you can test the tools.
|
|
72
79
|
|
|
73
|
-
## Environment Variables
|
|
74
|
-
|
|
75
|
-
- `INSIGHT_HUB_AUTH_TOKEN`: Required for Insight Hub tools. The Auth Token for Insight Hub.
|
|
76
|
-
- `REFLECT_API_TOKEN`: Required for Reflect tools. The Reflect Account API Key for Reflect-based tools.
|
|
77
|
-
- `API_HUB_API_KEY`: Required for API Hub tools. The API Key for API Hub tools.
|
|
78
|
-
- `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.
|
|
79
|
-
|
|
80
80
|
## Supported Tools
|
|
81
81
|
|
|
82
82
|
See individual guides for suggested prompts and supported tools and resources:
|
|
@@ -86,6 +86,15 @@ See individual guides for suggested prompts and supported tools and resources:
|
|
|
86
86
|
- [Reflect](./reflect/README.md)
|
|
87
87
|
- [API Hub](./api-hub/README.md)
|
|
88
88
|
|
|
89
|
+
## Environment Variables
|
|
90
|
+
|
|
91
|
+
- `INSIGHT_HUB_AUTH_TOKEN`: Required for Insight Hub tools. The Auth Token for Insight Hub.
|
|
92
|
+
- `REFLECT_API_TOKEN`: Required for Reflect tools. The Reflect Account API Key for Reflect-based tools.
|
|
93
|
+
- `API_HUB_API_KEY`: Required for API Hub tools. The API Key for API Hub tools.
|
|
94
|
+
- `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.
|
|
95
|
+
|
|
96
|
+
See individual guides for product-specific configuration via environment variables.
|
|
97
|
+
|
|
89
98
|
## Local Development
|
|
90
99
|
|
|
91
100
|
If you want to build and run the MCP server from source (for development or contribution):
|
|
@@ -111,9 +120,7 @@ Update your `.vscode/mcp.json` to point to your local build:
|
|
|
111
120
|
"command": "node",
|
|
112
121
|
"args": ["<PATH_TO_SMARTBEAR_MCP>/dist/index.js"],
|
|
113
122
|
"env": {
|
|
114
|
-
|
|
115
|
-
"REFLECT_API_TOKEN": "${input:reflect_api_token}",
|
|
116
|
-
"API_HUB_API_KEY": "${input:api_hub_api_key}"
|
|
123
|
+
// ...same as above...
|
|
117
124
|
}
|
|
118
125
|
}
|
|
119
126
|
},
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export function toolDescriptionTemplate(params) {
|
|
2
|
+
const { summary, useCases, examples, parameters, hints } = params;
|
|
3
|
+
let description = summary;
|
|
4
|
+
// Parameters (essential)
|
|
5
|
+
if (parameters.length > 0) {
|
|
6
|
+
description += `\n\n**Parameters:** ${parameters.map(p => `${p.name} (${p.type})${p.required ? ' *required*' : ''}`).join(', ')}`;
|
|
7
|
+
}
|
|
8
|
+
// Use Cases
|
|
9
|
+
if (useCases.length > 0) {
|
|
10
|
+
description += `\n\n**Use Cases:** ${useCases.map((uc, i) => `${i + 1}. ${uc}`).join(' ')}`;
|
|
11
|
+
}
|
|
12
|
+
// Examples
|
|
13
|
+
if (examples.length > 0) {
|
|
14
|
+
description += `\n\n**Examples:**\n` + examples.map((ex, idx) => `${idx + 1}. ${ex.description}\n\`\`\`json\n${JSON.stringify(ex.parameters, null, 2)}\n\`\`\`${ex.expectedOutput ? `\nExpected Output: ${ex.expectedOutput}` : ''}`).join('\n\n');
|
|
15
|
+
}
|
|
16
|
+
// Hints
|
|
17
|
+
if (hints.length > 0) {
|
|
18
|
+
description += `\n\n**Tips:** ${hints.map((hint, i) => `${i + 1}. ${hint}`).join(' ')}`;
|
|
19
|
+
}
|
|
20
|
+
return description.trim();
|
|
21
|
+
}
|
|
22
|
+
// Backward-compatible version of the original function
|
|
23
|
+
export function simpleToolDescriptionTemplate(summary, useCases, examples, hints) {
|
|
24
|
+
return toolDescriptionTemplate({
|
|
25
|
+
summary,
|
|
26
|
+
purpose: summary,
|
|
27
|
+
useCases,
|
|
28
|
+
examples: examples.map(example => ({
|
|
29
|
+
description: example,
|
|
30
|
+
parameters: {}
|
|
31
|
+
})),
|
|
32
|
+
parameters: [],
|
|
33
|
+
hints
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
// Helper function to create parameter descriptions
|
|
37
|
+
export function createParameter(name, type, required, description, options = {}) {
|
|
38
|
+
return {
|
|
39
|
+
name,
|
|
40
|
+
type,
|
|
41
|
+
required,
|
|
42
|
+
description,
|
|
43
|
+
examples: options.examples,
|
|
44
|
+
constraints: options.constraints
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// Helper function to create examples with proper structure
|
|
48
|
+
export function createExample(description, parameters, expectedOutput) {
|
|
49
|
+
return {
|
|
50
|
+
description,
|
|
51
|
+
parameters,
|
|
52
|
+
expectedOutput
|
|
53
|
+
};
|
|
54
|
+
}
|
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,74 @@ 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
|
+
import { toolDescriptionTemplate, createParameter, createExample } from "../common/templates.js";
|
|
10
|
+
const HUB_PREFIX = "00000";
|
|
11
|
+
const HUB_API_ENDPOINT = "https://api.insighthub.smartbear.com";
|
|
12
|
+
const DEFAULT_API_ENDPOINT = "https://api.bugsnag.com";
|
|
13
|
+
const cacheKeys = {
|
|
14
|
+
ORG: "insight_hub_org",
|
|
15
|
+
PROJECTS: "insight_hub_projects",
|
|
16
|
+
CURRENT_PROJECT: "insight_hub_current_project",
|
|
17
|
+
PROJECT_EVENT_FILTERS: "insight_hub_project_event_filters",
|
|
18
|
+
};
|
|
6
19
|
export class InsightHubClient {
|
|
7
20
|
currentUserApi;
|
|
8
21
|
errorsApi;
|
|
9
|
-
|
|
22
|
+
cache;
|
|
23
|
+
projectApi;
|
|
24
|
+
projectApiKey;
|
|
25
|
+
constructor(token, projectApiKey, endpoint) {
|
|
26
|
+
if (!endpoint) {
|
|
27
|
+
if (projectApiKey && projectApiKey.startsWith(HUB_PREFIX)) {
|
|
28
|
+
endpoint = HUB_API_ENDPOINT;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
endpoint = DEFAULT_API_ENDPOINT;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
10
34
|
const config = new Configuration({
|
|
11
35
|
authToken: token,
|
|
12
36
|
headers: {
|
|
13
37
|
"User-Agent": `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`,
|
|
14
38
|
"Content-Type": "application/json",
|
|
39
|
+
"X-Bugsnag-API": "true",
|
|
40
|
+
"X-Version": "2",
|
|
15
41
|
},
|
|
16
|
-
basePath:
|
|
42
|
+
basePath: endpoint,
|
|
17
43
|
});
|
|
18
44
|
this.currentUserApi = new CurrentUserAPI(config);
|
|
19
45
|
this.errorsApi = new ErrorAPI(config);
|
|
46
|
+
this.cache = new NodeCache();
|
|
47
|
+
this.projectApi = new ProjectAPI(config);
|
|
48
|
+
this.projectApiKey = projectApiKey;
|
|
49
|
+
}
|
|
50
|
+
async initialize() {
|
|
51
|
+
const orgs = await this.listOrgs();
|
|
52
|
+
if (!orgs || orgs.length === 0) {
|
|
53
|
+
throw new Error("No organizations found for the current user.");
|
|
54
|
+
}
|
|
55
|
+
// We should only have one org
|
|
56
|
+
this.cache.set(cacheKeys.ORG, orgs[0]);
|
|
57
|
+
const projects = await this.listProjects(orgs[0].id);
|
|
58
|
+
this.cache.set(cacheKeys.PROJECTS, projects);
|
|
59
|
+
if (this.projectApiKey) {
|
|
60
|
+
const project = projects.find((project) => project.api_key === this.projectApiKey);
|
|
61
|
+
if (!project) {
|
|
62
|
+
throw new Error(`Project with API key ${this.projectApiKey} not found in organization ${orgs[0].name}.`);
|
|
63
|
+
}
|
|
64
|
+
this.cache.set(cacheKeys.CURRENT_PROJECT, project);
|
|
65
|
+
const projectFields = await this.listProjectEventFields(project.id);
|
|
66
|
+
if (!projectFields || projectFields.length === 0) {
|
|
67
|
+
throw new Error(`No event fields found for project ${project.name}.`);
|
|
68
|
+
}
|
|
69
|
+
this.cache.set(cacheKeys.PROJECT_EVENT_FILTERS, projectFields);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async listProjectEventFields(projectId) {
|
|
73
|
+
return this.projectApi.listProjectEventFields(projectId);
|
|
20
74
|
}
|
|
21
75
|
async listOrgs() {
|
|
22
76
|
return this.currentUserApi.listUserOrganizations();
|
|
@@ -28,6 +82,25 @@ export class InsightHubClient {
|
|
|
28
82
|
};
|
|
29
83
|
return this.currentUserApi.getOrganizationProjects(orgId, options);
|
|
30
84
|
}
|
|
85
|
+
// This method retrieves all projects for the organization stored in the cache.
|
|
86
|
+
// If no projects are found in the cache, it fetches them from the API and
|
|
87
|
+
// stores them in the cache for future use.
|
|
88
|
+
// It throws an error if no organizations are found in the cache.
|
|
89
|
+
async getProjects() {
|
|
90
|
+
let projects = this.cache.get(cacheKeys.PROJECTS);
|
|
91
|
+
if (!projects) {
|
|
92
|
+
const org = this.cache.get(cacheKeys.ORG);
|
|
93
|
+
if (!org) {
|
|
94
|
+
throw new Error("No organization found in cache.");
|
|
95
|
+
}
|
|
96
|
+
projects = await this.listProjects(org.id);
|
|
97
|
+
this.cache.set(cacheKeys.PROJECTS, projects);
|
|
98
|
+
}
|
|
99
|
+
if (!projects) {
|
|
100
|
+
throw new Error("No projects found.");
|
|
101
|
+
}
|
|
102
|
+
return projects;
|
|
103
|
+
}
|
|
31
104
|
async getErrorDetails(projectId, errorId) {
|
|
32
105
|
return this.errorsApi.viewErrorOnProject(projectId, errorId);
|
|
33
106
|
}
|
|
@@ -42,29 +115,104 @@ export class InsightHubClient {
|
|
|
42
115
|
const eventDetails = await Promise.all(projects.map((p) => this.getProjectEvent(p.id, eventId).catch(_e => null)));
|
|
43
116
|
return eventDetails.find(event => !!event);
|
|
44
117
|
}
|
|
118
|
+
async listProjectErrors(projectId, filters) {
|
|
119
|
+
return this.errorsApi.listProjectErrors(projectId, { filters });
|
|
120
|
+
}
|
|
45
121
|
registerTools(server) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
122
|
+
if (!this.projectApiKey) {
|
|
123
|
+
server.registerTool("list_insight_hub_projects", {
|
|
124
|
+
description: toolDescriptionTemplate({
|
|
125
|
+
summary: "List all projects in the organization with optional pagination",
|
|
126
|
+
purpose: "Retrieve available projects for browsing and selecting which project to analyze",
|
|
127
|
+
useCases: [
|
|
128
|
+
"Browse available projects when no specific project API key is configured",
|
|
129
|
+
"Find project IDs needed for other tools",
|
|
130
|
+
"Get an overview of all projects in the organization"
|
|
131
|
+
],
|
|
132
|
+
parameters: [
|
|
133
|
+
createParameter("page_size", "number", false, "Number of projects to return per page for pagination", {
|
|
134
|
+
examples: ["10", "25", "50"]
|
|
135
|
+
}),
|
|
136
|
+
createParameter("page", "number", false, "Page number to return (starts from 1)", {
|
|
137
|
+
examples: ["1", "2", "3"]
|
|
138
|
+
})
|
|
139
|
+
],
|
|
140
|
+
examples: [
|
|
141
|
+
createExample("Get first 10 projects", {
|
|
142
|
+
page_size: 10,
|
|
143
|
+
page: 1
|
|
144
|
+
}, "JSON array of project objects with IDs, names, and metadata"),
|
|
145
|
+
createExample("Get all projects (no pagination)", {}, "JSON array of all available projects")
|
|
146
|
+
],
|
|
147
|
+
hints: [
|
|
148
|
+
"Use pagination for organizations with many projects to avoid large responses",
|
|
149
|
+
"Project IDs from this list can be used with other tools when no project API key is configured"
|
|
150
|
+
]
|
|
151
|
+
}),
|
|
152
|
+
inputSchema: {
|
|
153
|
+
page_size: z.number().optional().describe("Number of projects to return per page"),
|
|
154
|
+
page: z.number().optional().describe("Page number to return"),
|
|
155
|
+
}
|
|
156
|
+
}, async (args, _extra) => {
|
|
157
|
+
try {
|
|
158
|
+
let projects = await this.getProjects();
|
|
159
|
+
if (!projects || projects.length === 0) {
|
|
160
|
+
return {
|
|
161
|
+
content: [{ type: "text", text: "No projects found." }],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (args.page_size || args.page) {
|
|
165
|
+
const pageSize = args.page_size || 10;
|
|
166
|
+
const page = args.page || 1;
|
|
167
|
+
projects = projects.slice((page - 1) * pageSize, page * pageSize);
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
content: [{ type: "text", text: JSON.stringify(projects) }],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
Bugsnag.notify(e);
|
|
175
|
+
throw e;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
server.registerTool("get_insight_hub_error", {
|
|
180
|
+
description: toolDescriptionTemplate({
|
|
181
|
+
summary: "Get detailed information about a specific error from a project",
|
|
182
|
+
purpose: "Retrieve error details including metadata, events, and context for debugging",
|
|
183
|
+
useCases: [
|
|
184
|
+
"Investigate a specific error found through list_insight_hub_project_errors",
|
|
185
|
+
"Get error details for debugging and root cause analysis",
|
|
186
|
+
"Retrieve error metadata for incident reports and documentation"
|
|
187
|
+
],
|
|
188
|
+
parameters: [
|
|
189
|
+
createParameter("errorId", "string", true, "Unique identifier of the error to retrieve", {
|
|
190
|
+
examples: ["6863e2af8c857c0a5023b411"]
|
|
191
|
+
}),
|
|
192
|
+
...(!this.projectApiKey ? [
|
|
193
|
+
createParameter("projectId", "string", false, "ID of the project containing the error (optional if project API key is configured)")
|
|
194
|
+
] : [])
|
|
195
|
+
],
|
|
196
|
+
examples: [
|
|
197
|
+
createExample("Get details for a specific error", {
|
|
198
|
+
errorId: "6863e2af8c857c0a5023b411"
|
|
199
|
+
}, "JSON object with error details including message, stack trace, occurrence count, and metadata")
|
|
200
|
+
],
|
|
201
|
+
hints: [
|
|
202
|
+
"Error IDs can be found using the list_insight_hub_project_errors tool",
|
|
203
|
+
"Use this after filtering errors to get detailed information about specific errors"
|
|
204
|
+
]
|
|
205
|
+
}),
|
|
206
|
+
inputSchema: {
|
|
207
|
+
errorId: z.string().describe("ID of the error to fetch"),
|
|
208
|
+
...(!this.projectApiKey ? { projectId: z.string().optional().describe("ID of the project") } : {}),
|
|
58
209
|
}
|
|
59
|
-
});
|
|
60
|
-
server.tool("get_insight_hub_error", "Get error details from a project", {
|
|
61
|
-
projectId: z.string().describe("ID of the project"),
|
|
62
|
-
errorId: z.string().describe("ID of the error to fetch"),
|
|
63
210
|
}, async (args, _extra) => {
|
|
64
211
|
try {
|
|
65
|
-
|
|
212
|
+
const projectId = typeof args.projectId === 'string' ? args.projectId : this.cache.get(cacheKeys.CURRENT_PROJECT)?.id;
|
|
213
|
+
if (!projectId || !args.errorId)
|
|
66
214
|
throw new Error("Both projectId and errorId arguments are required");
|
|
67
|
-
const response = await this.getErrorDetails(
|
|
215
|
+
const response = await this.getErrorDetails(projectId, args.errorId);
|
|
68
216
|
return {
|
|
69
217
|
content: [{ type: "text", text: JSON.stringify(response) }],
|
|
70
218
|
};
|
|
@@ -74,8 +222,33 @@ export class InsightHubClient {
|
|
|
74
222
|
throw e;
|
|
75
223
|
}
|
|
76
224
|
});
|
|
77
|
-
server.
|
|
78
|
-
|
|
225
|
+
server.registerTool("get_insight_hub_error_latest_event", {
|
|
226
|
+
description: toolDescriptionTemplate({
|
|
227
|
+
summary: "Get the most recent event of a specific error",
|
|
228
|
+
purpose: "Retrieve the latest event (occurrence) of an error to understand when it last happened and its details",
|
|
229
|
+
useCases: [
|
|
230
|
+
"Get the most recent occurrence of an error for immediate debugging",
|
|
231
|
+
"Understand the latest context and stack trace for an ongoing issue",
|
|
232
|
+
"Check when an error last occurred and with what parameters"
|
|
233
|
+
],
|
|
234
|
+
parameters: [
|
|
235
|
+
createParameter("errorId", "string", true, "Unique identifier of the error to get the latest event for", {
|
|
236
|
+
examples: ["6863e2af8c857c0a5023b411"]
|
|
237
|
+
})
|
|
238
|
+
],
|
|
239
|
+
examples: [
|
|
240
|
+
createExample("Get the latest event for an error", {
|
|
241
|
+
errorId: "6863e2af8c857c0a5023b411"
|
|
242
|
+
}, "JSON object with the most recent event details including timestamp, stack trace, and context")
|
|
243
|
+
],
|
|
244
|
+
hints: [
|
|
245
|
+
"This shows the most recent occurrence - use get_insight_hub_error for aggregated details of all events grouped into the error",
|
|
246
|
+
"The event includes detailed context like user information, request data, and environment details"
|
|
247
|
+
]
|
|
248
|
+
}),
|
|
249
|
+
inputSchema: {
|
|
250
|
+
errorId: z.string().describe("ID of the error to get the latest event for"),
|
|
251
|
+
}
|
|
79
252
|
}, async (args, _extra) => {
|
|
80
253
|
try {
|
|
81
254
|
if (!args.errorId)
|
|
@@ -90,8 +263,38 @@ export class InsightHubClient {
|
|
|
90
263
|
throw e;
|
|
91
264
|
}
|
|
92
265
|
});
|
|
93
|
-
server.
|
|
94
|
-
|
|
266
|
+
server.registerTool("get_insight_hub_event_details", {
|
|
267
|
+
description: toolDescriptionTemplate({
|
|
268
|
+
summary: "Get detailed information about a specific event using its Insight Hub dashboard URL",
|
|
269
|
+
purpose: "Retrieve event details directly from an Insight Hub web interface URL for quick debugging",
|
|
270
|
+
useCases: [
|
|
271
|
+
"Get event details when given an Insight Hub dashboard URL from a user or notification",
|
|
272
|
+
"Extract event information from shared links or browser URLs",
|
|
273
|
+
"Quick lookup of event details without needing separate project and event IDs"
|
|
274
|
+
],
|
|
275
|
+
parameters: [
|
|
276
|
+
createParameter("link", "string", true, "Full URL to the event details page in the Insight Hub dashboard (web interface)", {
|
|
277
|
+
examples: [
|
|
278
|
+
"https://app.bugsnag.com/my-org/my-project/errors/6863e2af8c857c0a5023b411?event_id=6863e2af012caf1d5c320000"
|
|
279
|
+
],
|
|
280
|
+
constraints: [
|
|
281
|
+
"Must be a valid Insight Hub dashboard URL containing project slug and event_id parameter"
|
|
282
|
+
]
|
|
283
|
+
})
|
|
284
|
+
],
|
|
285
|
+
examples: [
|
|
286
|
+
createExample("Get event details from Insight Hub URL", {
|
|
287
|
+
link: "https://app.bugsnag.com/my-org/my-project/errors/6863e2af8c857c0a5023b411?event_id=6863e2af012caf1d5c320000"
|
|
288
|
+
}, "JSON object with complete event details including stack trace, metadata, and context")
|
|
289
|
+
],
|
|
290
|
+
hints: [
|
|
291
|
+
"The URL must contain both project slug in the path and event_id in query parameters",
|
|
292
|
+
"This is useful when users share Insight Hub dashboard URLs and you need to extract the event data"
|
|
293
|
+
]
|
|
294
|
+
}),
|
|
295
|
+
inputSchema: {
|
|
296
|
+
link: z.string().describe("Link to the event details"),
|
|
297
|
+
}
|
|
95
298
|
}, async (args, _extra) => {
|
|
96
299
|
try {
|
|
97
300
|
if (!args.link)
|
|
@@ -102,9 +305,14 @@ export class InsightHubClient {
|
|
|
102
305
|
if (!projectSlug || !eventId)
|
|
103
306
|
throw new Error("Both projectSlug and eventId must be present in the link");
|
|
104
307
|
// get the project id from list of projects
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
308
|
+
const projects = this.cache.get("insight_hub_projects");
|
|
309
|
+
if (!projects) {
|
|
310
|
+
throw new Error("No projects found in cache.");
|
|
311
|
+
}
|
|
312
|
+
const projectId = projects.find((p) => p.slug === projectSlug)?.id;
|
|
313
|
+
if (!projectId) {
|
|
314
|
+
throw new Error("Project with the specified slug not found.");
|
|
315
|
+
}
|
|
108
316
|
const response = await this.getProjectEvent(projectId, eventId);
|
|
109
317
|
return {
|
|
110
318
|
content: [{ type: "text", text: JSON.stringify(response) }],
|
|
@@ -115,15 +323,75 @@ export class InsightHubClient {
|
|
|
115
323
|
throw e;
|
|
116
324
|
}
|
|
117
325
|
});
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
326
|
+
// Dynamically infer the filters schema from cached project event fields
|
|
327
|
+
server.registerTool("list_insight_hub_project_errors", {
|
|
328
|
+
description: toolDescriptionTemplate({
|
|
329
|
+
summary: "List and search errors in a project using customizable filters",
|
|
330
|
+
purpose: "Retrieve filtered list of errors from a project for analysis, debugging, and reporting",
|
|
331
|
+
useCases: [
|
|
332
|
+
"Debug recent application errors by filtering for open errors in the last 7 days",
|
|
333
|
+
"Generate error reports for stakeholders by filtering specific error types or severity levels",
|
|
334
|
+
"Monitor error trends over time using date range filters",
|
|
335
|
+
"Find errors affecting specific users or environments using metadata filters"
|
|
336
|
+
],
|
|
337
|
+
parameters: [
|
|
338
|
+
createParameter("filters", "FilterObject", false, "Apply filters to narrow down the error list. Use get_project_event_filters to discover available filter fields", {
|
|
339
|
+
examples: [
|
|
340
|
+
'{"error.status": [{"type": "eq", "value": "open"}]}',
|
|
341
|
+
'{"event.since": [{"type": "eq", "value": "7d"}]} // Relative time: last 7 days',
|
|
342
|
+
'{"event.since": [{"type": "eq", "value": "2018-05-20T00:00:00Z"}]} // ISO 8601 UTC format',
|
|
343
|
+
'{"user.email": [{"type": "eq", "value": "user@example.com"}]}'
|
|
344
|
+
],
|
|
345
|
+
constraints: [
|
|
346
|
+
"Time filters support ISO 8601 format (e.g. 2018-05-20T00:00:00Z) or relative format (e.g. 7d, 24h)",
|
|
347
|
+
"ISO 8601 times must be in UTC and use extended format",
|
|
348
|
+
"Relative time periods: h (hours), d (days)"
|
|
349
|
+
]
|
|
350
|
+
}),
|
|
351
|
+
...(this.projectApiKey ? [] : [
|
|
352
|
+
createParameter("projectId", "string", true, "ID of the project to query for errors")
|
|
353
|
+
])
|
|
354
|
+
],
|
|
355
|
+
examples: [
|
|
356
|
+
createExample("Find errors affecting a specific user in the last 24 hours", {
|
|
357
|
+
filters: {
|
|
358
|
+
"user.email": [{ "type": "eq", "value": "user@example.com" }],
|
|
359
|
+
"event.since": [{ "type": "eq", "value": "24h" }]
|
|
360
|
+
}
|
|
361
|
+
})
|
|
362
|
+
],
|
|
363
|
+
hints: [
|
|
364
|
+
"Use get_project_event_filters tool first to discover valid filter field names for your project",
|
|
365
|
+
"Combine multiple filters to narrow results - filters are applied with AND logic",
|
|
366
|
+
"For time filters: use relative format (7d, 24h) for recent periods or ISO 8601 UTC format (2018-05-20T00:00:00Z) for specific dates",
|
|
367
|
+
"Common time filters: event.since (from this time), event.before (until this time)"
|
|
368
|
+
]
|
|
369
|
+
}),
|
|
370
|
+
inputSchema: {
|
|
371
|
+
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."),
|
|
372
|
+
// Conditionally add projectId only when no project API key is configured
|
|
373
|
+
...(this.projectApiKey ? {} : {
|
|
374
|
+
projectId: z.string().describe("ID of the project"),
|
|
375
|
+
}),
|
|
376
|
+
}
|
|
377
|
+
}, async (args, _extra) => {
|
|
121
378
|
try {
|
|
379
|
+
const projectId = typeof args.projectId === 'string' ? args.projectId : this.cache.get(cacheKeys.CURRENT_PROJECT)?.id;
|
|
380
|
+
if (!projectId)
|
|
381
|
+
throw new Error("projectId argument is required");
|
|
382
|
+
// Optionally, validate filter keys against cached event fields
|
|
383
|
+
const eventFields = this.cache.get(cacheKeys.PROJECT_EVENT_FILTERS) || [];
|
|
384
|
+
if (args.filters) {
|
|
385
|
+
const validKeys = new Set(eventFields.map(f => f.display_id));
|
|
386
|
+
for (const key of Object.keys(args.filters)) {
|
|
387
|
+
if (!validKeys.has(key)) {
|
|
388
|
+
throw new Error(`Invalid filter key: ${key}`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const response = await this.listProjectErrors(projectId, args.filters);
|
|
122
393
|
return {
|
|
123
|
-
|
|
124
|
-
uri: uri.href,
|
|
125
|
-
text: JSON.stringify(await this.listOrgs())
|
|
126
|
-
}]
|
|
394
|
+
content: [{ type: "text", text: JSON.stringify(response) }],
|
|
127
395
|
};
|
|
128
396
|
}
|
|
129
397
|
catch (e) {
|
|
@@ -131,6 +399,41 @@ export class InsightHubClient {
|
|
|
131
399
|
throw e;
|
|
132
400
|
}
|
|
133
401
|
});
|
|
402
|
+
server.registerTool("get_project_event_filters", {
|
|
403
|
+
description: toolDescriptionTemplate({
|
|
404
|
+
summary: "Get available event filter fields for the current project",
|
|
405
|
+
purpose: "Discover valid filter field names and options that can be used with list_insight_hub_project_errors",
|
|
406
|
+
useCases: [
|
|
407
|
+
"Discover what filter fields are available before searching for errors",
|
|
408
|
+
"Find the correct field names for filtering by user, environment, or custom metadata",
|
|
409
|
+
"Understand filter options and data types for building complex queries"
|
|
410
|
+
],
|
|
411
|
+
parameters: [],
|
|
412
|
+
examples: [
|
|
413
|
+
createExample("Get all available filter fields", {}, "JSON array of EventField objects containing display_id, custom flag, and filter/pivot options")
|
|
414
|
+
],
|
|
415
|
+
hints: [
|
|
416
|
+
"Use this tool before list_insight_hub_project_errors to understand available filters",
|
|
417
|
+
"Look for display_id field in the response - these are the field names to use in filters"
|
|
418
|
+
]
|
|
419
|
+
}),
|
|
420
|
+
inputSchema: {}
|
|
421
|
+
}, async (_args, _extra) => {
|
|
422
|
+
try {
|
|
423
|
+
const eventFields = this.cache.get(cacheKeys.PROJECT_EVENT_FILTERS);
|
|
424
|
+
if (!eventFields)
|
|
425
|
+
throw new Error("No event filters found in cache.");
|
|
426
|
+
return {
|
|
427
|
+
content: [{ type: "text", text: JSON.stringify(eventFields) }],
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
catch (e) {
|
|
431
|
+
Bugsnag.notify(e);
|
|
432
|
+
throw e;
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
registerResources(server) {
|
|
134
437
|
server.resource("insight_hub_event", new ResourceTemplate("insighthub://event/{id}", { list: undefined }), async (uri, { id }) => {
|
|
135
438
|
try {
|
|
136
439
|
return {
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartbear/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "MCP server for interacting SmartBear Products",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"smartbear",
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@bugsnag/js": "^8.2.0",
|
|
38
|
-
"@modelcontextprotocol/sdk": "1.
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.15.0",
|
|
39
|
+
"node-cache": "^5.1.2"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
42
|
"@eslint/js": "^9.29.0",
|
package/insight-hub/README.md
CHANGED
|
@@ -2,34 +2,40 @@
|
|
|
2
2
|
|
|
3
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
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.
|
|
6
|
+
|
|
7
|
+
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. This allows the MCP server to pre-cache your project's custom filters for better filtering prompts.
|
|
8
|
+
|
|
5
9
|
## Example prompts
|
|
6
10
|
|
|
7
|
-
- "Help me fix this crash
|
|
8
|
-
- "What are my
|
|
11
|
+
- "Help me fix this crash: https://app.bugsnag.com/my-org/my-project/errors/1a2b3c4d5e6f7g8h9i0j1k2l?&event_id=1a2b3c4d5e6f7g8h9i0j1k2l"
|
|
12
|
+
- "What are my latest events my project?"
|
|
13
|
+
- "Which errors occurred in the last 7 days?"
|
|
9
14
|
|
|
10
15
|
## Environment Variables
|
|
11
16
|
|
|
12
|
-
- `INSIGHT_HUB_AUTH_TOKEN`: Required
|
|
13
|
-
- `
|
|
17
|
+
- `INSIGHT_HUB_AUTH_TOKEN`: (Required) The auth token for your account from your Insight Hub dashboard, under **Personal auth tokens** in user settings.
|
|
18
|
+
- `INSIGHT_HUB_PROJECT_API_KEY`: (Recommended) The API key for the Insight Hub project you wish to interact with. Use this to scope all operations to a single project.
|
|
19
|
+
- `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`).
|
|
14
20
|
|
|
15
21
|
## Tools
|
|
16
22
|
|
|
17
23
|
1. `list_insight_hub_projects`
|
|
18
24
|
- List all projects in an organization.
|
|
25
|
+
- Multi-project mode only.
|
|
19
26
|
2. `get_insight_hub_error`
|
|
20
27
|
- Get error details from a project.
|
|
21
28
|
3. `get_insight_hub_error_latest_event`
|
|
22
29
|
- Get the latest event for an error.
|
|
23
30
|
4. `get_insight_hub_event_details`
|
|
24
|
-
- Get details of a specific event
|
|
31
|
+
- Get details of a specific event.
|
|
32
|
+
5. `get_project_event_filters`
|
|
33
|
+
- List the filters available for a project.
|
|
34
|
+
6. `list_insight_hub_project_errors`
|
|
35
|
+
- List and filter the errors from a project.
|
|
25
36
|
|
|
26
37
|
## Resources
|
|
27
38
|
|
|
28
|
-
- **insight_hub_orgs**
|
|
29
|
-
- URI: `insighthub://orgs`
|
|
30
|
-
- Description: Lists all organizations available to your Insight Hub account in JSON format.
|
|
31
|
-
- Example usage: Retrieve a list of all organizations to get their IDs for use with other tools.
|
|
32
|
-
|
|
33
39
|
- **insight_hub_event**
|
|
34
40
|
- URI Template: `insighthub://event/{id}`
|
|
35
41
|
- Description: Fetches details for a specific event by its event ID. Returns the event details in JSON format.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartbear/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "MCP server for interacting SmartBear Products",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"smartbear",
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@bugsnag/js": "^8.2.0",
|
|
38
|
-
"@modelcontextprotocol/sdk": "1.
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.15.0",
|
|
39
|
+
"node-cache": "^5.1.2"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
42
|
"@eslint/js": "^9.29.0",
|