@smartbear/mcp 0.9.0 → 0.11.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 +38 -11
- package/dist/bugsnag/client/api/index.js +2 -0
- package/dist/bugsnag/client/filters.js +0 -6
- package/dist/bugsnag/client.js +238 -380
- package/dist/bugsnag/input-schemas.js +51 -0
- package/dist/collaborator/client.js +377 -0
- package/dist/common/cache.js +63 -0
- package/dist/common/client-registry.js +128 -0
- package/dist/common/register-clients.js +31 -0
- package/dist/common/server.js +77 -28
- package/dist/common/transport-http.js +377 -0
- package/dist/common/transport-stdio.js +43 -0
- package/dist/index.js +18 -60
- package/dist/pactflow/client/tools.js +4 -4
- package/dist/pactflow/client.js +39 -19
- package/dist/qmetry/client/auto-resolve.js +22 -0
- package/dist/qmetry/client/handlers.js +18 -4
- package/dist/qmetry/client/issues.js +98 -1
- package/dist/qmetry/client/project.js +18 -1
- package/dist/qmetry/client/testcase.js +79 -1
- package/dist/qmetry/client/testsuite.js +156 -1
- package/dist/qmetry/client/tools/index.js +17 -0
- package/dist/qmetry/client/tools/issue-tools.js +545 -0
- package/dist/qmetry/client/tools/project-tools.js +348 -0
- package/dist/qmetry/client/tools/requirement-tools.js +530 -0
- package/dist/qmetry/client/tools/testcase-tools.js +526 -0
- package/dist/qmetry/client/tools/testsuite-tools.js +772 -0
- package/dist/qmetry/client/tools/types.js +1 -0
- package/dist/qmetry/client.js +33 -11
- package/dist/qmetry/config/constants.js +14 -0
- package/dist/qmetry/config/rest-endpoints.js +10 -0
- package/dist/qmetry/types/common.js +287 -2
- package/dist/qmetry/types/issues.js +11 -1
- package/dist/qmetry/types/project.js +7 -0
- package/dist/qmetry/types/testcase.js +6 -0
- package/dist/qmetry/types/testsuite.js +19 -1
- package/dist/reflect/client.js +10 -4
- package/dist/{api-hub → swagger}/client/api.js +190 -2
- package/dist/{api-hub → swagger}/client/configuration.js +6 -1
- package/dist/swagger/client/index.js +6 -0
- package/dist/{api-hub → swagger}/client/portal-types.js +126 -0
- package/dist/swagger/client/tools.js +161 -0
- package/dist/swagger/client/user-management-types.js +24 -0
- package/dist/swagger/client.js +141 -0
- package/dist/swagger/config-utils.js +18 -0
- package/dist/zephyr/client.js +44 -6
- package/dist/zephyr/common/api-client.js +8 -0
- package/dist/zephyr/common/rest-api-schemas.js +5174 -0
- package/dist/zephyr/tool/priority/get-priorities.js +43 -0
- package/dist/zephyr/tool/project/get-project.js +39 -0
- package/dist/zephyr/tool/project/get-projects.js +7 -13
- package/dist/zephyr/tool/status/get-statuses.js +49 -0
- package/dist/zephyr/tool/test-case/get-test-case.js +39 -0
- package/dist/zephyr/tool/test-case/get-test-cases.js +64 -0
- package/dist/zephyr/tool/test-cycle/get-test-cycle.js +39 -0
- package/dist/zephyr/tool/test-cycle/get-test-cycles.js +72 -0
- package/dist/zephyr/tool/test-execution/get-test-execution.js +39 -0
- package/package.json +2 -2
- package/dist/api-hub/client/index.js +0 -5
- package/dist/api-hub/client/tools.js +0 -104
- package/dist/api-hub/client.js +0 -98
- package/dist/qmetry/client/tools.js +0 -1673
- package/dist/zephyr/common/types.js +0 -35
- /package/dist/{api-hub → swagger}/client/registry-types.js +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
const filterValueSchema = z.object({
|
|
3
|
+
type: z.enum(["eq", "ne", "empty"]),
|
|
4
|
+
value: z.union([z.string(), z.boolean(), z.number()]),
|
|
5
|
+
});
|
|
6
|
+
/**
|
|
7
|
+
* A collection of input parameter schemas for reuse between tools.
|
|
8
|
+
* Add new entries when common parameters are identified.
|
|
9
|
+
*/
|
|
10
|
+
export const toolInputParameters = {
|
|
11
|
+
empty: z.object({}).describe("No parameters are required for this tool"),
|
|
12
|
+
projectId: z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("ID of the project. This is optional if a current project is set and is used to set the current project for BugSnag tools."),
|
|
16
|
+
errorId: z.string().describe("Unique identifier of the error"),
|
|
17
|
+
releaseId: z.string().describe("ID of the release"),
|
|
18
|
+
buildId: z.string().describe("ID of the build"),
|
|
19
|
+
direction: z
|
|
20
|
+
.enum(["asc", "desc"])
|
|
21
|
+
.describe("Sort direction for ordering results")
|
|
22
|
+
.default("desc"),
|
|
23
|
+
filters: z
|
|
24
|
+
.record(z.array(filterValueSchema))
|
|
25
|
+
.describe("Apply filters to narrow down the error list. Use the List Project Event Filters tool to discover available filter fields. " +
|
|
26
|
+
"Time filters support extended ISO 8601 format (e.g. 2018-05-20T00:00:00Z) or relative format (e.g. 7d, 24h).")
|
|
27
|
+
.default({
|
|
28
|
+
"event.since": [{ type: "eq", value: "30d" }],
|
|
29
|
+
"error.status": [{ type: "eq", value: "open" }],
|
|
30
|
+
}),
|
|
31
|
+
nextUrl: z
|
|
32
|
+
.string()
|
|
33
|
+
.describe("URL for retrieving the next page of results. Use the value in the previous response to get the next page when more results are available. " +
|
|
34
|
+
"Only values provided in the output from this tool can be used. Do not attempt to construct it manually.")
|
|
35
|
+
.optional(),
|
|
36
|
+
page: z.number().describe("Page number to return (starts from 1)").default(1),
|
|
37
|
+
perPage: z
|
|
38
|
+
.number()
|
|
39
|
+
.describe("How many results to return per page.")
|
|
40
|
+
.min(1)
|
|
41
|
+
.max(100)
|
|
42
|
+
.default(30),
|
|
43
|
+
releaseStage: z
|
|
44
|
+
.string()
|
|
45
|
+
.describe("Filter releases by this stage (e.g. production, staging), defaults to 'production'")
|
|
46
|
+
.default("production"),
|
|
47
|
+
sort: z
|
|
48
|
+
.enum(["first_seen", "last_seen", "events", "users", "unsorted"])
|
|
49
|
+
.describe("Field to sort the errors by")
|
|
50
|
+
.default("last_seen"),
|
|
51
|
+
};
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const ConfigurationSchema = z.object({
|
|
3
|
+
base_url: z.string().url().describe("Collaborator server base URL"),
|
|
4
|
+
username: z.string().describe("Collaborator username for authentication"),
|
|
5
|
+
login_ticket: z
|
|
6
|
+
.string()
|
|
7
|
+
.describe("Collaborator login ticket for authentication"),
|
|
8
|
+
});
|
|
9
|
+
export class CollaboratorClient {
|
|
10
|
+
name = "Collaborator";
|
|
11
|
+
toolPrefix = "collaborator";
|
|
12
|
+
configPrefix = "Collaborator";
|
|
13
|
+
config = ConfigurationSchema;
|
|
14
|
+
baseUrl;
|
|
15
|
+
username;
|
|
16
|
+
loginTicket;
|
|
17
|
+
async configure(_server, config, _cache) {
|
|
18
|
+
this.baseUrl = config.base_url;
|
|
19
|
+
this.username = config.username;
|
|
20
|
+
this.loginTicket = config.login_ticket;
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Calls the Collaborator API with the given commands, prepending authentication automatically.
|
|
25
|
+
* @param commands Array of Collaborator API commands (excluding authentication)
|
|
26
|
+
* @returns Raw Collaborator API response
|
|
27
|
+
*/
|
|
28
|
+
async call(commands) {
|
|
29
|
+
if (!this.baseUrl || !this.username || !this.loginTicket) {
|
|
30
|
+
throw new Error("Collaborator client not configured");
|
|
31
|
+
}
|
|
32
|
+
const url = `${this.baseUrl}/services/json/v1`;
|
|
33
|
+
// Always prepend authentication command automatically
|
|
34
|
+
const body = [
|
|
35
|
+
{
|
|
36
|
+
command: "SessionService.authenticate",
|
|
37
|
+
args: { login: this.username, ticket: this.loginTicket },
|
|
38
|
+
},
|
|
39
|
+
...commands,
|
|
40
|
+
];
|
|
41
|
+
const response = await fetch(url, {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: { "Content-Type": "application/json" },
|
|
44
|
+
body: JSON.stringify(body),
|
|
45
|
+
});
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new Error(`Collaborator API call failed: ${response.status} - ${await response.text()}`);
|
|
48
|
+
}
|
|
49
|
+
return await response.json();
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Registers the Collaborator API tool with the MCP server. Accepts commands (excluding authentication).
|
|
53
|
+
*/
|
|
54
|
+
registerTools(register, _getInput) {
|
|
55
|
+
// findReviewById tool
|
|
56
|
+
register({
|
|
57
|
+
title: "Find Collaborator Review By ID",
|
|
58
|
+
summary: "Finds a review in Collaborator by its review ID.",
|
|
59
|
+
inputSchema: z.object({
|
|
60
|
+
reviewId: z.string().describe("The Collaborator review ID to find."),
|
|
61
|
+
}),
|
|
62
|
+
}, async (args, _extra) => {
|
|
63
|
+
const { reviewId } = args;
|
|
64
|
+
const commands = [
|
|
65
|
+
{
|
|
66
|
+
command: "ReviewService.findReviewById",
|
|
67
|
+
args: { reviewId },
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
const result = await this.call(commands);
|
|
71
|
+
return {
|
|
72
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
// createReview tool
|
|
76
|
+
register({
|
|
77
|
+
title: "Create Collaborator Review",
|
|
78
|
+
summary: "Creates a new review in Collaborator. All parameters are optional.",
|
|
79
|
+
inputSchema: z.object({
|
|
80
|
+
creator: z
|
|
81
|
+
.string()
|
|
82
|
+
.optional()
|
|
83
|
+
.describe("Collaborator username of the review creator. Optional. Default: currently logged in user."),
|
|
84
|
+
title: z
|
|
85
|
+
.string()
|
|
86
|
+
.optional()
|
|
87
|
+
.describe("Title of the review. Optional. Default: null."),
|
|
88
|
+
templateName: z
|
|
89
|
+
.string()
|
|
90
|
+
.optional()
|
|
91
|
+
.describe("Review template name. Optional. Default: system default template."),
|
|
92
|
+
accessPolicy: z
|
|
93
|
+
.string()
|
|
94
|
+
.optional()
|
|
95
|
+
.describe("Access policy for the review. Optional. Default: ANYONE."),
|
|
96
|
+
}),
|
|
97
|
+
}, async (args, _extra) => {
|
|
98
|
+
const commandArgs = {};
|
|
99
|
+
if (args.creator !== undefined)
|
|
100
|
+
commandArgs.creator = args.creator;
|
|
101
|
+
if (args.title !== undefined)
|
|
102
|
+
commandArgs.title = args.title;
|
|
103
|
+
if (args.templateName !== undefined)
|
|
104
|
+
commandArgs.templateName = args.templateName;
|
|
105
|
+
if (args.accessPolicy !== undefined)
|
|
106
|
+
commandArgs.accessPolicy = args.accessPolicy;
|
|
107
|
+
const commands = [
|
|
108
|
+
{
|
|
109
|
+
command: "ReviewService.createReview",
|
|
110
|
+
args: commandArgs,
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
const result = await this.call(commands);
|
|
114
|
+
return {
|
|
115
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
// rejectReview tool
|
|
119
|
+
register({
|
|
120
|
+
title: "Reject Collaborator Review",
|
|
121
|
+
summary: "Rejects a review in Collaborator by its review ID and reason.",
|
|
122
|
+
inputSchema: z.object({
|
|
123
|
+
reviewId: z
|
|
124
|
+
.union([z.string(), z.number()])
|
|
125
|
+
.describe("The Collaborator review ID to reject."),
|
|
126
|
+
reason: z.string().describe("Reason for rejecting the review."),
|
|
127
|
+
}),
|
|
128
|
+
}, async (args, _extra) => {
|
|
129
|
+
const { reviewId, reason } = args;
|
|
130
|
+
const commands = [
|
|
131
|
+
{
|
|
132
|
+
command: "ReviewService.reject",
|
|
133
|
+
args: {
|
|
134
|
+
reviewId,
|
|
135
|
+
reason,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
const result = await this.call(commands);
|
|
140
|
+
return {
|
|
141
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
register({
|
|
145
|
+
title: "ReviewService Action",
|
|
146
|
+
summary: "Invoke any ReviewService method by name and arguments. For finishReviewPhase and waitOnPhase, provide reviewId (required) and until (optional, defaults to 'ANY').",
|
|
147
|
+
inputSchema: z.object({
|
|
148
|
+
action: z.enum([
|
|
149
|
+
"moveReviewToAnnotatePhase",
|
|
150
|
+
"cancel",
|
|
151
|
+
"reopen",
|
|
152
|
+
"uncancel",
|
|
153
|
+
]),
|
|
154
|
+
args: z.record(z.any()),
|
|
155
|
+
}),
|
|
156
|
+
}, async (params, _extra) => {
|
|
157
|
+
const { action, args } = params;
|
|
158
|
+
const commands = [{ command: `ReviewService.${action}`, args }];
|
|
159
|
+
const result = await this.call(commands);
|
|
160
|
+
return {
|
|
161
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
// getReviews tool
|
|
165
|
+
register({
|
|
166
|
+
title: "Get Collaborator Reviews",
|
|
167
|
+
summary: "Retrieves reviews from Collaborator using ReviewService.getReviews. All parameters are optional and only provided ones are sent.",
|
|
168
|
+
inputSchema: z.object({
|
|
169
|
+
login: z
|
|
170
|
+
.string()
|
|
171
|
+
.optional()
|
|
172
|
+
.describe("Collaborator username to filter reviews."),
|
|
173
|
+
role: z
|
|
174
|
+
.string()
|
|
175
|
+
.optional()
|
|
176
|
+
.describe("Role to filter reviews (e.g., AUTHOR)."),
|
|
177
|
+
creator: z
|
|
178
|
+
.boolean()
|
|
179
|
+
.optional()
|
|
180
|
+
.describe("Whether to filter by creator."),
|
|
181
|
+
reviewPhase: z
|
|
182
|
+
.string()
|
|
183
|
+
.optional()
|
|
184
|
+
.describe("Review phase to filter (e.g., PLANNING)."),
|
|
185
|
+
fullInfo: z
|
|
186
|
+
.boolean()
|
|
187
|
+
.optional()
|
|
188
|
+
.describe("Whether to retrieve full review info."),
|
|
189
|
+
fromDate: z
|
|
190
|
+
.string()
|
|
191
|
+
.optional()
|
|
192
|
+
.describe('Minimal creation date in format "yyyy-MM-dd"'),
|
|
193
|
+
toDate: z
|
|
194
|
+
.string()
|
|
195
|
+
.optional()
|
|
196
|
+
.describe('Maximal creation date in format "yyyy-MM-dd"'),
|
|
197
|
+
}),
|
|
198
|
+
}, async (args, _extra) => {
|
|
199
|
+
const reviewArgs = {};
|
|
200
|
+
if (args.login !== undefined)
|
|
201
|
+
reviewArgs.login = args.login;
|
|
202
|
+
if (args.role !== undefined)
|
|
203
|
+
reviewArgs.role = args.role;
|
|
204
|
+
if (args.creator !== undefined)
|
|
205
|
+
reviewArgs.creator = args.creator;
|
|
206
|
+
if (args.reviewPhase !== undefined)
|
|
207
|
+
reviewArgs.reviewPhase = args.reviewPhase;
|
|
208
|
+
if (args.fullInfo !== undefined)
|
|
209
|
+
reviewArgs.fullInfo = args.fullInfo;
|
|
210
|
+
if (args.fromDate !== undefined)
|
|
211
|
+
reviewArgs.fromDate = args.fromDate;
|
|
212
|
+
if (args.toDate !== undefined)
|
|
213
|
+
reviewArgs.toDate = args.toDate;
|
|
214
|
+
const commands = [
|
|
215
|
+
{
|
|
216
|
+
command: "ReviewService.getReviews",
|
|
217
|
+
args: reviewArgs,
|
|
218
|
+
},
|
|
219
|
+
];
|
|
220
|
+
const result = await this.call(commands);
|
|
221
|
+
return {
|
|
222
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
// createIntegration tool
|
|
226
|
+
register({
|
|
227
|
+
title: "Create Collaborator Remote System Configuration",
|
|
228
|
+
summary: "Creates a remote system configuration in Collaborator (e.g., Bitbucket, GitHub, etc).",
|
|
229
|
+
inputSchema: z.object({
|
|
230
|
+
token: z
|
|
231
|
+
.string()
|
|
232
|
+
.describe("Remote system token, e.g., BITBUCKET, GITHUB, etc."),
|
|
233
|
+
title: z.string().describe("Remote system title."),
|
|
234
|
+
config: z
|
|
235
|
+
.string()
|
|
236
|
+
.describe("JSON string containing configuration parameters for the remote system."),
|
|
237
|
+
reviewTemplateId: z
|
|
238
|
+
.string()
|
|
239
|
+
.optional()
|
|
240
|
+
.describe("Optional review template ID used by this remote system."),
|
|
241
|
+
}),
|
|
242
|
+
}, async (args, _extra) => {
|
|
243
|
+
const { token, title, config, reviewTemplateId } = args;
|
|
244
|
+
const commandArgs = { token, title, config };
|
|
245
|
+
if (reviewTemplateId)
|
|
246
|
+
commandArgs.reviewTemplateId = reviewTemplateId;
|
|
247
|
+
const commands = [
|
|
248
|
+
{
|
|
249
|
+
command: "AdminRemoteSystemService.createIntegration",
|
|
250
|
+
args: commandArgs,
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
const result = await this.call(commands);
|
|
254
|
+
return {
|
|
255
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
// editIntegration tool
|
|
259
|
+
register({
|
|
260
|
+
title: "Edit Collaborator Remote System Configuration",
|
|
261
|
+
summary: "Edits parameters of an existing remote system configuration in Collaborator. Only title and config are editable after creation.",
|
|
262
|
+
inputSchema: z.object({
|
|
263
|
+
id: z
|
|
264
|
+
.string()
|
|
265
|
+
.describe("ID of the remote system Configuration to edit."),
|
|
266
|
+
title: z.string().optional().describe("Remote system title."),
|
|
267
|
+
config: z
|
|
268
|
+
.string()
|
|
269
|
+
.optional()
|
|
270
|
+
.describe("JSON string containing configuration parameters for the remote system."),
|
|
271
|
+
reviewTemplateId: z
|
|
272
|
+
.string()
|
|
273
|
+
.optional()
|
|
274
|
+
.describe("Optional review template ID used by this remote system."),
|
|
275
|
+
}),
|
|
276
|
+
}, async (args, _extra) => {
|
|
277
|
+
const { id, title, config, reviewTemplateId } = args;
|
|
278
|
+
const commandArgs = { id };
|
|
279
|
+
if (title)
|
|
280
|
+
commandArgs.title = title;
|
|
281
|
+
if (config)
|
|
282
|
+
commandArgs.config = config;
|
|
283
|
+
if (reviewTemplateId)
|
|
284
|
+
commandArgs.reviewTemplateId = reviewTemplateId;
|
|
285
|
+
const commands = [
|
|
286
|
+
{
|
|
287
|
+
command: "AdminRemoteSystemService.editIntegration",
|
|
288
|
+
args: commandArgs,
|
|
289
|
+
},
|
|
290
|
+
];
|
|
291
|
+
const result = await this.call(commands);
|
|
292
|
+
return {
|
|
293
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
294
|
+
};
|
|
295
|
+
});
|
|
296
|
+
// deleteIntegration tool
|
|
297
|
+
register({
|
|
298
|
+
title: "Delete Collaborator Remote System Configuration",
|
|
299
|
+
summary: "Deletes a remote system configuration in Collaborator by its ID.",
|
|
300
|
+
inputSchema: z.object({
|
|
301
|
+
id: z
|
|
302
|
+
.union([z.string(), z.number()])
|
|
303
|
+
.describe("ID of the remote system Configuration to delete."),
|
|
304
|
+
}),
|
|
305
|
+
}, async (args, _extra) => {
|
|
306
|
+
const commandArgs = {};
|
|
307
|
+
if (args.id !== undefined)
|
|
308
|
+
commandArgs.id =
|
|
309
|
+
typeof args.id === "string" && !Number.isNaN(Number(args.id))
|
|
310
|
+
? Number(args.id)
|
|
311
|
+
: args.id;
|
|
312
|
+
const commands = [
|
|
313
|
+
{
|
|
314
|
+
command: "AdminRemoteSystemService.deleteIntegration",
|
|
315
|
+
args: commandArgs,
|
|
316
|
+
},
|
|
317
|
+
];
|
|
318
|
+
const result = await this.call(commands);
|
|
319
|
+
return {
|
|
320
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
321
|
+
};
|
|
322
|
+
});
|
|
323
|
+
register({
|
|
324
|
+
title: "Update Collaborator Remote System Configuration Webhook",
|
|
325
|
+
summary: "Updates the webhook for a remote system configuration in Collaborator by its ID.",
|
|
326
|
+
inputSchema: z.object({
|
|
327
|
+
id: z
|
|
328
|
+
.union([z.string(), z.number()])
|
|
329
|
+
.describe("ID of the remote system Configuration to update the webhook for."),
|
|
330
|
+
}),
|
|
331
|
+
}, async (args, _extra) => {
|
|
332
|
+
const commandArgs = {};
|
|
333
|
+
if (args.id !== undefined)
|
|
334
|
+
commandArgs.id =
|
|
335
|
+
typeof args.id === "string" && !Number.isNaN(Number(args.id))
|
|
336
|
+
? Number(args.id)
|
|
337
|
+
: args.id;
|
|
338
|
+
const commands = [
|
|
339
|
+
{
|
|
340
|
+
command: "AdminRemoteSystemService.updateWebhook",
|
|
341
|
+
args: commandArgs,
|
|
342
|
+
},
|
|
343
|
+
];
|
|
344
|
+
const result = await this.call(commands);
|
|
345
|
+
return {
|
|
346
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
347
|
+
};
|
|
348
|
+
});
|
|
349
|
+
// Test connection tool
|
|
350
|
+
register({
|
|
351
|
+
title: "Test Collaborator Remote System Configuration Connection",
|
|
352
|
+
summary: "Tests the connection for a remote system configuration in Collaborator by its ID.",
|
|
353
|
+
inputSchema: z.object({
|
|
354
|
+
id: z
|
|
355
|
+
.union([z.string(), z.number()])
|
|
356
|
+
.describe("ID of the remote system Configuration to test connection for."),
|
|
357
|
+
}),
|
|
358
|
+
}, async (args, _extra) => {
|
|
359
|
+
const commandArgs = {};
|
|
360
|
+
if (args.id !== undefined)
|
|
361
|
+
commandArgs.id =
|
|
362
|
+
typeof args.id === "string" && !Number.isNaN(Number(args.id))
|
|
363
|
+
? Number(args.id)
|
|
364
|
+
: args.id;
|
|
365
|
+
const commands = [
|
|
366
|
+
{
|
|
367
|
+
command: "AdminRemoteSystemService.testConnection",
|
|
368
|
+
args: commandArgs,
|
|
369
|
+
},
|
|
370
|
+
];
|
|
371
|
+
const result = await this.call(commands);
|
|
372
|
+
return {
|
|
373
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
374
|
+
};
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import NodeCache from "node-cache";
|
|
2
|
+
/**
|
|
3
|
+
* Common cache service that can be shared across all clients.
|
|
4
|
+
* Wraps NodeCache and provides a way to disable caching entirely.
|
|
5
|
+
* Reads CACHE_ENABLED and CACHE_TTL environment variables for configuration.
|
|
6
|
+
*/
|
|
7
|
+
export class CacheService {
|
|
8
|
+
cache;
|
|
9
|
+
enabled;
|
|
10
|
+
constructor() {
|
|
11
|
+
// Read configuration from environment variables
|
|
12
|
+
this.enabled = process.env.CACHE_ENABLED !== "false";
|
|
13
|
+
const ttl = process.env.CACHE_TTL
|
|
14
|
+
? Number.parseInt(process.env.CACHE_TTL, 10)
|
|
15
|
+
: 86400; // Default 24 hours
|
|
16
|
+
this.cache = this.enabled
|
|
17
|
+
? new NodeCache({
|
|
18
|
+
stdTTL: ttl,
|
|
19
|
+
})
|
|
20
|
+
: null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get a value from the cache
|
|
24
|
+
*/
|
|
25
|
+
get(key) {
|
|
26
|
+
if (!this.enabled || !this.cache) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
return this.cache.get(key);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Set a value in the cache
|
|
33
|
+
*/
|
|
34
|
+
set(key, value) {
|
|
35
|
+
if (!this.enabled || !this.cache) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return this.cache.set(key, value);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Delete a value from the cache
|
|
42
|
+
*/
|
|
43
|
+
del(key) {
|
|
44
|
+
if (!this.enabled || !this.cache) {
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
return this.cache.del(key);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Check if caching is enabled
|
|
51
|
+
*/
|
|
52
|
+
isEnabled() {
|
|
53
|
+
return this.enabled;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Clear all cache entries
|
|
57
|
+
*/
|
|
58
|
+
flushAll() {
|
|
59
|
+
if (this.enabled && this.cache) {
|
|
60
|
+
this.cache.flushAll();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { ZodOptional, ZodString } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Central registry for all MCP clients
|
|
4
|
+
* Add new clients here to make them automatically available
|
|
5
|
+
*/
|
|
6
|
+
class ClientRegistry {
|
|
7
|
+
entries = [];
|
|
8
|
+
enabledClients = null;
|
|
9
|
+
/**
|
|
10
|
+
* Configure which clients should be enabled based on MCP_CLIENTS env var
|
|
11
|
+
* If not set or empty, all clients are enabled
|
|
12
|
+
* If set, should be comma-separated list of client names (case-insensitive)
|
|
13
|
+
*/
|
|
14
|
+
constructor() {
|
|
15
|
+
const enabledClientsEnv = process.env.MCP_CLIENTS?.trim();
|
|
16
|
+
if (!enabledClientsEnv) {
|
|
17
|
+
// Empty or not set = all clients enabled
|
|
18
|
+
this.enabledClients = null;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// Parse comma-separated list and normalize to lowercase for comparison
|
|
22
|
+
this.enabledClients = new Set(enabledClientsEnv
|
|
23
|
+
.split(",")
|
|
24
|
+
.map((name) => name.trim().toLowerCase())
|
|
25
|
+
.filter((name) => name.length > 0));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check if a client is enabled based on MCP_CLIENTS configuration
|
|
29
|
+
*/
|
|
30
|
+
isClientEnabled(name) {
|
|
31
|
+
if (this.enabledClients === null) {
|
|
32
|
+
return true; // All clients enabled
|
|
33
|
+
}
|
|
34
|
+
return this.enabledClients.has(name.toLowerCase());
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Validate if a config option is an Allowed Endpoint URL
|
|
38
|
+
* Supports both exact matches and regex patterns
|
|
39
|
+
* Patterns starting with / and ending with / are treated as regex
|
|
40
|
+
* @param zodType The Zod type definition for the config option
|
|
41
|
+
* @param value The actual config value to validate
|
|
42
|
+
*/
|
|
43
|
+
validateAllowedEndpoint(zodType, value) {
|
|
44
|
+
if (zodType instanceof ZodOptional) {
|
|
45
|
+
zodType = zodType._def.innerType;
|
|
46
|
+
}
|
|
47
|
+
if (zodType instanceof ZodString) {
|
|
48
|
+
if (zodType.isURL) {
|
|
49
|
+
const allowedEndpoints = process.env.MCP_ALLOWED_ENDPOINTS?.split(",");
|
|
50
|
+
if (allowedEndpoints) {
|
|
51
|
+
for (const endpoint of allowedEndpoints) {
|
|
52
|
+
const trimmedEndpoint = endpoint.trim();
|
|
53
|
+
// Check if this is a regex pattern (wrapped in /)
|
|
54
|
+
if (trimmedEndpoint.startsWith("/") &&
|
|
55
|
+
trimmedEndpoint.endsWith("/")) {
|
|
56
|
+
try {
|
|
57
|
+
const pattern = trimmedEndpoint.slice(1, -1); // Remove leading/trailing /
|
|
58
|
+
const regex = new RegExp(pattern);
|
|
59
|
+
if (regex.test(value)) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
console.warn(`Invalid regex pattern in MCP_ALLOWED_ENDPOINTS: ${trimmedEndpoint}, error: ${error}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// Exact match
|
|
69
|
+
if (value === trimmedEndpoint) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
throw new Error(`URL ${value} is not allowed`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Register a client class
|
|
81
|
+
* @param name Display name for the client (for logging)
|
|
82
|
+
*/
|
|
83
|
+
register(client) {
|
|
84
|
+
this.entries.push(client);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get all registered clients (filtered by MCP_CLIENTS if configured)
|
|
88
|
+
*/
|
|
89
|
+
getAll() {
|
|
90
|
+
return this.entries.filter((entry) => this.isClientEnabled(entry.name));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Configures all enabled clients on the given MCP server
|
|
94
|
+
* @param server The MCP server on which the client is registered
|
|
95
|
+
* @param getConfigValue A function that obtains a configuration value for the given client and requirement name
|
|
96
|
+
* @returns The number of clients successfully configured
|
|
97
|
+
*/
|
|
98
|
+
async configure(server, getConfigValue) {
|
|
99
|
+
let configuredCount = 0;
|
|
100
|
+
entryLoop: for (const entry of this.getAll()) {
|
|
101
|
+
const config = {};
|
|
102
|
+
for (const configKey of Object.keys(entry.config.shape)) {
|
|
103
|
+
const value = getConfigValue(entry, configKey);
|
|
104
|
+
if (value !== null) {
|
|
105
|
+
// validate if a config option is an Allowed Endpoint URL
|
|
106
|
+
this.validateAllowedEndpoint(entry.config.shape[configKey], value);
|
|
107
|
+
config[configKey] = value;
|
|
108
|
+
}
|
|
109
|
+
else if (!entry.config.shape[configKey].isOptional()) {
|
|
110
|
+
continue entryLoop; // Skip configuring this client - missing required config
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (await entry.configure(server, config)) {
|
|
114
|
+
server.addClient(entry);
|
|
115
|
+
configuredCount++;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return configuredCount;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Clear all registrations (useful for testing)
|
|
122
|
+
*/
|
|
123
|
+
clear() {
|
|
124
|
+
this.entries = [];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Create and export the singleton registry
|
|
128
|
+
export const clientRegistry = new ClientRegistry();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client registration module
|
|
3
|
+
*
|
|
4
|
+
* This file registers all available MCP clients with the client registry.
|
|
5
|
+
* To add a new client:
|
|
6
|
+
* 1. Import the client class
|
|
7
|
+
* 2. Call clientRegistry.register() with the client details
|
|
8
|
+
* 3. Specify if the client needs the MCP server instance or async initialization
|
|
9
|
+
*/
|
|
10
|
+
import { BugsnagClient } from "../bugsnag/client.js";
|
|
11
|
+
import { CollaboratorClient } from "../collaborator/client.js";
|
|
12
|
+
import { PactflowClient } from "../pactflow/client.js";
|
|
13
|
+
import { QmetryClient } from "../qmetry/client.js";
|
|
14
|
+
import { ReflectClient } from "../reflect/client.js";
|
|
15
|
+
import { SwaggerClient } from "../swagger/client.js";
|
|
16
|
+
import { ZephyrClient } from "../zephyr/client.js";
|
|
17
|
+
import { clientRegistry } from "./client-registry.js";
|
|
18
|
+
// Register Reflect client
|
|
19
|
+
clientRegistry.register(new ReflectClient());
|
|
20
|
+
// Register Bugsnag client
|
|
21
|
+
clientRegistry.register(new BugsnagClient());
|
|
22
|
+
// Register Swagger client
|
|
23
|
+
clientRegistry.register(new SwaggerClient());
|
|
24
|
+
// Register PactFlow/Pact Broker client
|
|
25
|
+
clientRegistry.register(new PactflowClient());
|
|
26
|
+
// Register QMetry client
|
|
27
|
+
clientRegistry.register(new QmetryClient());
|
|
28
|
+
// Register Zephyr client
|
|
29
|
+
clientRegistry.register(new ZephyrClient());
|
|
30
|
+
// Register Collaborator client
|
|
31
|
+
clientRegistry.register(new CollaboratorClient());
|