@smartbear/mcp 0.2.2 → 0.4.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 +207 -60
- package/dist/api-hub/client.js +298 -52
- package/dist/common/server.js +145 -0
- package/dist/index.js +31 -22
- package/dist/insight-hub/client/api/CurrentUser.js +9 -3
- package/dist/insight-hub/client/api/Error.js +81 -0
- package/dist/insight-hub/client/api/Project.js +19 -1
- package/dist/insight-hub/client/api/base.js +10 -2
- package/dist/insight-hub/client.js +524 -357
- package/dist/package.json +15 -5
- package/dist/pactflow/client/ai.js +127 -0
- package/dist/pactflow/client/base.js +1 -0
- package/dist/pactflow/client/tools.js +46 -0
- package/dist/pactflow/client.js +132 -0
- package/dist/reflect/client.js +100 -18
- package/dist/tests/unit/common/server.test.js +319 -0
- package/dist/tests/unit/insight-hub/api-utilities.test.js +31 -0
- package/dist/tests/unit/insight-hub/client.test.js +852 -0
- package/dist/tests/unit/insight-hub/filters.test.js +93 -0
- package/dist/tests/unit/pactflow/ai.test.js +21 -0
- package/dist/tests/unit/pactflow/client.test.js +67 -0
- package/dist/tests/unit/pactflow/tools.test.js +34 -0
- package/dist/vitest.config.js +57 -0
- package/package.json +15 -5
- package/api-hub/README.md +0 -29
- package/assets/smartbear-logo-dark.svg +0 -17
- package/assets/smartbear-logo-light.svg +0 -17
- package/dist/common/templates.js +0 -54
- package/insight-hub/README.md +0 -42
- package/reflect/README.md +0 -25
package/dist/api-hub/client.js
CHANGED
|
@@ -3,6 +3,8 @@ import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info.js";
|
|
|
3
3
|
// Tool definitions for API Hub API client
|
|
4
4
|
export class ApiHubClient {
|
|
5
5
|
headers;
|
|
6
|
+
name = "API Hub";
|
|
7
|
+
prefix = "api_hub";
|
|
6
8
|
constructor(token) {
|
|
7
9
|
this.headers = {
|
|
8
10
|
"Authorization": `Bearer ${token}`,
|
|
@@ -82,99 +84,343 @@ export class ApiHubClient {
|
|
|
82
84
|
});
|
|
83
85
|
return response.json();
|
|
84
86
|
}
|
|
85
|
-
registerTools(
|
|
86
|
-
|
|
87
|
+
registerTools(register, _getInput) {
|
|
88
|
+
register({
|
|
89
|
+
title: "List Portals",
|
|
90
|
+
summary: "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.",
|
|
91
|
+
parameters: [],
|
|
92
|
+
}, async (_args, _extra) => {
|
|
87
93
|
const response = await this.getPortals();
|
|
88
94
|
return {
|
|
89
95
|
content: [{ type: "text", text: JSON.stringify(response) }],
|
|
90
96
|
};
|
|
91
97
|
});
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
register({
|
|
99
|
+
title: "Create Portal",
|
|
100
|
+
summary: "Create a new portal within API Hub.",
|
|
101
|
+
parameters: [
|
|
102
|
+
{
|
|
103
|
+
name: "name",
|
|
104
|
+
type: z.string(),
|
|
105
|
+
required: false,
|
|
106
|
+
description: "The portal name.",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "subdomain",
|
|
110
|
+
type: z.string(),
|
|
111
|
+
required: true,
|
|
112
|
+
description: "The portal subdomain.",
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "offline",
|
|
116
|
+
type: z.boolean(),
|
|
117
|
+
required: false,
|
|
118
|
+
description: "If set to true the portal will not be visible to customers.",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: "routing",
|
|
122
|
+
type: z.string(),
|
|
123
|
+
required: false,
|
|
124
|
+
description: "Determines the routing strategy ('browser' or 'proxy').",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: "credentialsEnabled",
|
|
128
|
+
type: z.string(),
|
|
129
|
+
required: false,
|
|
130
|
+
description: "Indicates if credentials are enabled for the portal.",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "swaggerHubOrganizationId",
|
|
134
|
+
type: z.string(),
|
|
135
|
+
required: true,
|
|
136
|
+
description: "The corresponding API Hub (formerly SwaggerHub) organization UUID.",
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "openapiRenderer",
|
|
140
|
+
type: z.string(),
|
|
141
|
+
required: false,
|
|
142
|
+
description: "Portal level setting for the OpenAPI renderer. SWAGGER_UI - Use the Swagger UI renderer. ELEMENTS - Use the Elements renderer. TOGGLE - Switch between the two renderers with elements set as the default.",
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: "pageContentFormat",
|
|
146
|
+
type: z.string(),
|
|
147
|
+
required: false,
|
|
148
|
+
description: "The format of the page content.",
|
|
149
|
+
}
|
|
150
|
+
],
|
|
101
151
|
}, async (args, _extra) => {
|
|
102
|
-
const
|
|
152
|
+
const createPortalArgs = args;
|
|
153
|
+
const response = await this.createPortal(createPortalArgs);
|
|
103
154
|
return {
|
|
104
155
|
content: [{ type: "text", text: JSON.stringify(response) }],
|
|
105
156
|
};
|
|
106
157
|
});
|
|
107
|
-
|
|
108
|
-
|
|
158
|
+
register({
|
|
159
|
+
title: "Get Portal",
|
|
160
|
+
summary: "Retrieve information about a specific portal.",
|
|
161
|
+
parameters: [
|
|
162
|
+
{
|
|
163
|
+
name: "portalId",
|
|
164
|
+
type: z.string(),
|
|
165
|
+
description: "Portal UUID or subdomain.",
|
|
166
|
+
required: true
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
}, async (args, _extra) => {
|
|
170
|
+
const portalArgs = args;
|
|
171
|
+
const response = await this.getPortal(portalArgs.portalId);
|
|
109
172
|
return {
|
|
110
173
|
content: [{ type: "text", text: JSON.stringify(response) }],
|
|
111
174
|
};
|
|
112
175
|
});
|
|
113
|
-
|
|
114
|
-
|
|
176
|
+
register({
|
|
177
|
+
title: "Delete Portal",
|
|
178
|
+
summary: "Delete a specific portal.",
|
|
179
|
+
parameters: [
|
|
180
|
+
{
|
|
181
|
+
name: "portalId",
|
|
182
|
+
type: z.string(),
|
|
183
|
+
description: "Portal UUID or subdomain.",
|
|
184
|
+
required: true
|
|
185
|
+
}
|
|
186
|
+
],
|
|
187
|
+
}, async (args, _extra) => {
|
|
188
|
+
const portalArgs = args;
|
|
189
|
+
await this.deletePortal(portalArgs.portalId);
|
|
115
190
|
return {
|
|
116
191
|
content: [{ type: "text", text: "Portal deleted successfully." }],
|
|
117
192
|
};
|
|
118
193
|
});
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
194
|
+
register({
|
|
195
|
+
title: "Update Portal",
|
|
196
|
+
summary: "Update a specific portal's configuration.",
|
|
197
|
+
parameters: [
|
|
198
|
+
{
|
|
199
|
+
name: "portalId",
|
|
200
|
+
type: z.string(),
|
|
201
|
+
description: "Portal UUID or subdomain.",
|
|
202
|
+
required: true
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: "name",
|
|
206
|
+
type: z.string(),
|
|
207
|
+
description: "The portal name.",
|
|
208
|
+
required: false
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: "subdomain",
|
|
212
|
+
type: z.string(),
|
|
213
|
+
description: "The portal subdomain.",
|
|
214
|
+
required: false
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: "customDomain",
|
|
218
|
+
type: z.boolean(),
|
|
219
|
+
description: "Indicates if the portal has a custom domain.",
|
|
220
|
+
required: false
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: "gtmKey",
|
|
224
|
+
type: z.string(),
|
|
225
|
+
description: "Google Tag Manager key for the portal.",
|
|
226
|
+
required: false
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: "offline",
|
|
230
|
+
type: z.boolean(),
|
|
231
|
+
description: "If set to true the portal will not be visible to customers.",
|
|
232
|
+
required: false
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: "routing",
|
|
236
|
+
type: z.string(),
|
|
237
|
+
description: "Determines the routing strategy ('browser' or 'proxy').",
|
|
238
|
+
required: false
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
name: "credentialsEnabled",
|
|
242
|
+
type: z.boolean(),
|
|
243
|
+
description: "Indicates if credentials are enabled for the portal.",
|
|
244
|
+
required: false
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: "openapiRenderer",
|
|
248
|
+
type: z.string(),
|
|
249
|
+
description: "Portal level setting for the OpenAPI renderer. SWAGGER_UI - Use the Swagger UI renderer. ELEMENTS - Use the Elements renderer. TOGGLE - Switch between the two renderers with elements set as the default.",
|
|
250
|
+
required: false
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: "pageContentFormat",
|
|
254
|
+
type: z.string(),
|
|
255
|
+
description: "The format of the page content.",
|
|
256
|
+
required: false
|
|
257
|
+
}
|
|
258
|
+
],
|
|
130
259
|
}, async (args, _extra) => {
|
|
131
|
-
const
|
|
260
|
+
const updatePortalArgs = args;
|
|
261
|
+
const response = await this.updatePortal(updatePortalArgs.portalId, updatePortalArgs);
|
|
132
262
|
return {
|
|
133
263
|
content: [{ type: "text", text: JSON.stringify(response) }],
|
|
134
264
|
};
|
|
135
265
|
});
|
|
136
|
-
|
|
137
|
-
|
|
266
|
+
register({
|
|
267
|
+
title: "List Portal Products",
|
|
268
|
+
summary: "Get products for a specific portal that match your criteria.",
|
|
269
|
+
parameters: [
|
|
270
|
+
{
|
|
271
|
+
name: "portalId",
|
|
272
|
+
type: z.string(),
|
|
273
|
+
description: "Portal UUID or subdomain.",
|
|
274
|
+
required: true
|
|
275
|
+
}
|
|
276
|
+
],
|
|
277
|
+
}, async (args, _extra) => {
|
|
278
|
+
const portalArgs = args;
|
|
279
|
+
const response = await this.getPortalProducts(portalArgs.portalId);
|
|
138
280
|
return {
|
|
139
281
|
content: [{ type: "text", text: JSON.stringify(response) }],
|
|
140
282
|
};
|
|
141
283
|
});
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
284
|
+
register({
|
|
285
|
+
title: "Create Portal Product",
|
|
286
|
+
summary: "Create a new product for a specific portal.",
|
|
287
|
+
parameters: [
|
|
288
|
+
{
|
|
289
|
+
name: "portalId",
|
|
290
|
+
type: z.string(),
|
|
291
|
+
description: "Portal UUID or subdomain.",
|
|
292
|
+
required: true
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
name: "type",
|
|
296
|
+
type: z.string(),
|
|
297
|
+
description: "Product type (Allowed values: 'new', 'copy').",
|
|
298
|
+
required: true
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
name: "name",
|
|
302
|
+
type: z.string(),
|
|
303
|
+
description: "Product name.",
|
|
304
|
+
required: true
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
name: "slug",
|
|
308
|
+
type: z.string(),
|
|
309
|
+
description: "URL component for this product. Must be unique within the portal.",
|
|
310
|
+
required: true
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
name: "description",
|
|
314
|
+
type: z.string(),
|
|
315
|
+
description: "Product description.",
|
|
316
|
+
required: false
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
name: "public",
|
|
320
|
+
type: z.boolean(),
|
|
321
|
+
description: "Indicates if the product is public.",
|
|
322
|
+
required: false
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
name: "hidden",
|
|
326
|
+
type: z.string(),
|
|
327
|
+
description: "Indicates if the product is hidden.",
|
|
328
|
+
required: false
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
name: "role",
|
|
332
|
+
type: z.boolean(),
|
|
333
|
+
description: "Indicates if the product has a role.",
|
|
334
|
+
required: false
|
|
335
|
+
}
|
|
336
|
+
],
|
|
151
337
|
}, async (args, _extra) => {
|
|
152
|
-
const
|
|
338
|
+
const createProductArgs = args;
|
|
339
|
+
const response = await this.createPortalProduct(createProductArgs.portalId, createProductArgs);
|
|
153
340
|
return {
|
|
154
341
|
content: [{ type: "text", text: JSON.stringify(response) }],
|
|
155
342
|
};
|
|
156
343
|
});
|
|
157
|
-
|
|
158
|
-
|
|
344
|
+
register({
|
|
345
|
+
title: "Get Portal Product",
|
|
346
|
+
summary: "Retrieve information about a specific product resource.",
|
|
347
|
+
parameters: [
|
|
348
|
+
{
|
|
349
|
+
name: "productId",
|
|
350
|
+
type: z.string(),
|
|
351
|
+
description: "Product UUID, or identifier in the format.",
|
|
352
|
+
required: true
|
|
353
|
+
}
|
|
354
|
+
],
|
|
355
|
+
}, async (args, _extra) => {
|
|
356
|
+
const productArgs = args;
|
|
357
|
+
const response = await this.getPortalProduct(productArgs.productId);
|
|
159
358
|
return {
|
|
160
359
|
content: [{ type: "text", text: JSON.stringify(response) }],
|
|
161
360
|
};
|
|
162
361
|
});
|
|
163
|
-
|
|
164
|
-
|
|
362
|
+
register({
|
|
363
|
+
title: "Delete Portal Product",
|
|
364
|
+
summary: "Delete a product from a specific portal",
|
|
365
|
+
parameters: [
|
|
366
|
+
{
|
|
367
|
+
name: "productId",
|
|
368
|
+
type: z.string(),
|
|
369
|
+
description: "Product UUID, or identifier in the format.",
|
|
370
|
+
required: true
|
|
371
|
+
}
|
|
372
|
+
],
|
|
373
|
+
}, async (args, _extra) => {
|
|
374
|
+
const productArgs = args;
|
|
375
|
+
await this.deletePortalProduct(productArgs.productId);
|
|
165
376
|
return {
|
|
166
377
|
content: [{ type: "text", text: "Product deleted successfully." }],
|
|
167
378
|
};
|
|
168
379
|
});
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
380
|
+
register({
|
|
381
|
+
title: "Update Portal Product",
|
|
382
|
+
summary: "Update a product's settings within a specific portal.",
|
|
383
|
+
parameters: [
|
|
384
|
+
{
|
|
385
|
+
name: "productId",
|
|
386
|
+
type: z.string(),
|
|
387
|
+
description: "Product UUID, or identifier in the format.",
|
|
388
|
+
required: true
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
name: "name",
|
|
392
|
+
type: z.string(),
|
|
393
|
+
description: "Product name.",
|
|
394
|
+
required: false
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: "slug",
|
|
398
|
+
type: z.string(),
|
|
399
|
+
description: "URL component for this product. Must be unique within the portal.",
|
|
400
|
+
required: false
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
name: "description",
|
|
404
|
+
type: z.string(),
|
|
405
|
+
description: "Product description.",
|
|
406
|
+
required: false
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
name: "public",
|
|
410
|
+
type: z.boolean(),
|
|
411
|
+
description: "Indicates if the product is public.",
|
|
412
|
+
required: false
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
name: "hidden",
|
|
416
|
+
type: z.string(),
|
|
417
|
+
description: "Indicates if the product is hidden.",
|
|
418
|
+
required: false
|
|
419
|
+
}
|
|
420
|
+
],
|
|
176
421
|
}, async (args, _extra) => {
|
|
177
|
-
const
|
|
422
|
+
const updateProductArgs = args;
|
|
423
|
+
const response = await this.updatePortalProduct(updateProductArgs.productId, updateProductArgs);
|
|
178
424
|
return {
|
|
179
425
|
content: [{ type: "text", text: JSON.stringify(response) }],
|
|
180
426
|
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { ZodAny, ZodArray, ZodBoolean, ZodEnum, ZodLiteral, ZodNumber, ZodObject, ZodString, ZodUnion, } from "zod";
|
|
3
|
+
import Bugsnag from "../common/bugsnag.js";
|
|
4
|
+
import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "./info.js";
|
|
5
|
+
export class SmartBearMcpServer extends McpServer {
|
|
6
|
+
constructor() {
|
|
7
|
+
super({
|
|
8
|
+
name: MCP_SERVER_NAME,
|
|
9
|
+
version: MCP_SERVER_VERSION,
|
|
10
|
+
}, {
|
|
11
|
+
capabilities: {
|
|
12
|
+
resources: { listChanged: true }, // Server supports dynamic resource lists
|
|
13
|
+
tools: { listChanged: true }, // Server supports dynamic tool lists
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
addClient(client) {
|
|
18
|
+
client.registerTools((params, cb) => {
|
|
19
|
+
const toolName = `${client.prefix}_${params.title.replace(/\s+/g, "_").toLowerCase()}`;
|
|
20
|
+
const toolTitle = `${client.name}: ${params.title}`;
|
|
21
|
+
return super.registerTool(toolName, {
|
|
22
|
+
title: toolTitle,
|
|
23
|
+
description: this.getDescription(params),
|
|
24
|
+
inputSchema: this.getInputSchema(params),
|
|
25
|
+
annotations: this.getAnnotations(toolTitle, params),
|
|
26
|
+
}, async (args, extra) => {
|
|
27
|
+
try {
|
|
28
|
+
return await cb(args, extra);
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
Bugsnag.notify(e);
|
|
32
|
+
throw e;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}, (params, options) => {
|
|
36
|
+
return this.server.elicitInput(params, options);
|
|
37
|
+
});
|
|
38
|
+
if (client.registerResources) {
|
|
39
|
+
client.registerResources((name, path, cb) => {
|
|
40
|
+
return super.registerResource(name, new ResourceTemplate(`${client.prefix}://${name}/${path}`, { list: undefined }), {}, async (url, variables, extra) => {
|
|
41
|
+
try {
|
|
42
|
+
return await cb(url, variables, extra);
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
Bugsnag.notify(e);
|
|
46
|
+
throw e;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
getAnnotations(toolTitle, params) {
|
|
53
|
+
const annotations = {
|
|
54
|
+
title: toolTitle,
|
|
55
|
+
readOnlyHint: params.readOnly ?? true,
|
|
56
|
+
destructiveHint: params.destructive ?? false,
|
|
57
|
+
idempotentHint: params.idempotent ?? true,
|
|
58
|
+
openWorldHint: params.openWorld ?? false,
|
|
59
|
+
};
|
|
60
|
+
return annotations;
|
|
61
|
+
}
|
|
62
|
+
getInputSchema(params) {
|
|
63
|
+
const args = {};
|
|
64
|
+
for (const param of params.parameters ?? []) {
|
|
65
|
+
args[param.name] = param.type;
|
|
66
|
+
if (param.description) {
|
|
67
|
+
args[param.name] = args[param.name].describe(param.description);
|
|
68
|
+
}
|
|
69
|
+
if (!param.required) {
|
|
70
|
+
args[param.name] = args[param.name].optional();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (params.zodSchema && params.zodSchema instanceof ZodObject) {
|
|
74
|
+
for (const key of Object.keys(params.zodSchema.shape)) {
|
|
75
|
+
const field = params.zodSchema.shape[key];
|
|
76
|
+
args[key] = field;
|
|
77
|
+
if (field.description) {
|
|
78
|
+
args[key] = args[key].describe(field.description);
|
|
79
|
+
}
|
|
80
|
+
if (field.isOptional()) {
|
|
81
|
+
args[key] = args[key].optional();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return args;
|
|
86
|
+
}
|
|
87
|
+
getDescription(params) {
|
|
88
|
+
const { summary, useCases, examples, parameters, zodSchema, hints, outputFormat } = params;
|
|
89
|
+
let description = summary;
|
|
90
|
+
// Parameters if available otherwise use zodSchema
|
|
91
|
+
if ((parameters ?? []).length > 0) {
|
|
92
|
+
description += `\n\n**Parameters:**\n${parameters?.map(p => `- ${p.name} (${this.getReadableTypeName(p.type)})${p.required ? ' *required*' : ''}` +
|
|
93
|
+
`${p.description ? `: ${p.description}` : ''}` +
|
|
94
|
+
`${p.examples ? ` (e.g. ${p.examples.join(', ')})` : ''}` +
|
|
95
|
+
`${p.constraints ? `\n - ${p.constraints.join('\n - ')}` : ''}`).join('\n')}`;
|
|
96
|
+
}
|
|
97
|
+
if (zodSchema && zodSchema instanceof ZodObject) {
|
|
98
|
+
description += "\n\n**Parameters:**\n";
|
|
99
|
+
for (const key of Object.keys(zodSchema.shape)) {
|
|
100
|
+
const field = zodSchema.shape[key];
|
|
101
|
+
description += this.formatParameterDescription(key, field);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (outputFormat) {
|
|
105
|
+
description += `\n\n**Output Format:** ${outputFormat}`;
|
|
106
|
+
}
|
|
107
|
+
// Use Cases
|
|
108
|
+
if (useCases && useCases.length > 0) {
|
|
109
|
+
description += `\n\n**Use Cases:** ${useCases.map((uc, i) => `${i + 1}. ${uc}`).join(' ')}`;
|
|
110
|
+
}
|
|
111
|
+
// Examples
|
|
112
|
+
if (examples && examples.length > 0) {
|
|
113
|
+
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');
|
|
114
|
+
}
|
|
115
|
+
// Hints
|
|
116
|
+
if (hints && hints.length > 0) {
|
|
117
|
+
description += `\n\n**Hints:** ${hints.map((hint, i) => `${i + 1}. ${hint}`).join(' ')}`;
|
|
118
|
+
}
|
|
119
|
+
return description.trim();
|
|
120
|
+
}
|
|
121
|
+
formatParameterDescription(key, field) {
|
|
122
|
+
return `- ${key} (${this.getReadableTypeName(field)})${field.isOptional() ? '' : ' *required*'}${field.description ? `: ${field.description}` : ''}${key === "examples" && field instanceof ZodEnum ? ` (e.g. ${Object.keys(field.enum).join(', ')})` : ''}${key === "constraints" && field instanceof ZodEnum ? `\n - ${Object.keys(field.enum).join('\n - ')}` : ''} \n`;
|
|
123
|
+
}
|
|
124
|
+
getReadableTypeName(zodType) {
|
|
125
|
+
if (zodType instanceof ZodString)
|
|
126
|
+
return 'string';
|
|
127
|
+
if (zodType instanceof ZodNumber)
|
|
128
|
+
return 'number';
|
|
129
|
+
if (zodType instanceof ZodBoolean)
|
|
130
|
+
return 'boolean';
|
|
131
|
+
if (zodType instanceof ZodArray)
|
|
132
|
+
return 'array';
|
|
133
|
+
if (zodType instanceof ZodObject)
|
|
134
|
+
return 'object';
|
|
135
|
+
if (zodType instanceof ZodEnum)
|
|
136
|
+
return 'enum';
|
|
137
|
+
if (zodType instanceof ZodLiteral)
|
|
138
|
+
return 'literal';
|
|
139
|
+
if (zodType instanceof ZodUnion)
|
|
140
|
+
return 'union';
|
|
141
|
+
if (zodType instanceof ZodAny)
|
|
142
|
+
return 'any';
|
|
143
|
+
return 'any';
|
|
144
|
+
}
|
|
145
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
3
|
import Bugsnag from "./common/bugsnag.js";
|
|
5
|
-
import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "./common/info.js";
|
|
6
4
|
import { InsightHubClient } from "./insight-hub/client.js";
|
|
7
5
|
import { ReflectClient } from "./reflect/client.js";
|
|
8
6
|
import { ApiHubClient } from "./api-hub/client.js";
|
|
7
|
+
import { SmartBearMcpServer } from "./common/server.js";
|
|
8
|
+
import { PactflowClient } from "./pactflow/client.js";
|
|
9
9
|
// This is used to report errors in the MCP server itself
|
|
10
10
|
// If you want to use your own BugSnag API key, set the MCP_SERVER_INSIGHT_HUB_API_KEY environment variable
|
|
11
11
|
const McpServerBugsnagAPIKey = process.env.MCP_SERVER_INSIGHT_HUB_API_KEY;
|
|
@@ -13,36 +13,45 @@ if (McpServerBugsnagAPIKey) {
|
|
|
13
13
|
Bugsnag.start(McpServerBugsnagAPIKey);
|
|
14
14
|
}
|
|
15
15
|
async function main() {
|
|
16
|
-
const server = new
|
|
17
|
-
name: MCP_SERVER_NAME,
|
|
18
|
-
version: MCP_SERVER_VERSION,
|
|
19
|
-
}, {
|
|
20
|
-
capabilities: {
|
|
21
|
-
resources: { listChanged: true }, // Server supports dynamic resource lists
|
|
22
|
-
tools: { listChanged: true }, // Server supports dynamic tool lists
|
|
23
|
-
},
|
|
24
|
-
});
|
|
16
|
+
const server = new SmartBearMcpServer();
|
|
25
17
|
const reflectToken = process.env.REFLECT_API_TOKEN;
|
|
26
18
|
const insightHubToken = process.env.INSIGHT_HUB_AUTH_TOKEN;
|
|
27
19
|
const apiHubToken = process.env.API_HUB_API_KEY;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
20
|
+
const pactBrokerToken = process.env.PACT_BROKER_TOKEN;
|
|
21
|
+
const pactBrokerUrl = process.env.PACT_BROKER_BASE_URL;
|
|
22
|
+
const pactBrokerUsername = process.env.PACT_BROKER_USERNAME;
|
|
23
|
+
const pactBrokerPassword = process.env.PACT_BROKER_PASSWORD;
|
|
24
|
+
let client_defined = false;
|
|
32
25
|
if (reflectToken) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
reflectClient.registerResources(server);
|
|
26
|
+
server.addClient(new ReflectClient(reflectToken));
|
|
27
|
+
client_defined = true;
|
|
36
28
|
}
|
|
37
29
|
if (insightHubToken) {
|
|
38
30
|
const insightHubClient = new InsightHubClient(insightHubToken, process.env.INSIGHT_HUB_PROJECT_API_KEY, process.env.INSIGHT_HUB_ENDPOINT);
|
|
39
31
|
await insightHubClient.initialize();
|
|
40
|
-
|
|
41
|
-
|
|
32
|
+
server.addClient(insightHubClient);
|
|
33
|
+
client_defined = true;
|
|
42
34
|
}
|
|
43
35
|
if (apiHubToken) {
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
server.addClient(new ApiHubClient(apiHubToken));
|
|
37
|
+
client_defined = true;
|
|
38
|
+
}
|
|
39
|
+
if (pactBrokerUrl) {
|
|
40
|
+
if (pactBrokerToken) {
|
|
41
|
+
server.addClient(new PactflowClient(pactBrokerToken, pactBrokerUrl, "pactflow"));
|
|
42
|
+
client_defined = true;
|
|
43
|
+
}
|
|
44
|
+
else if (pactBrokerUsername && pactBrokerPassword) {
|
|
45
|
+
server.addClient(new PactflowClient({ username: pactBrokerUsername, password: pactBrokerPassword }, pactBrokerUrl, "pact_broker"));
|
|
46
|
+
client_defined = true;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
console.error("If the Pact Broker base URL is specified, you must specify either (a) a PactFlow token, or (b) a Pact Broker username and password pair.");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!client_defined) {
|
|
53
|
+
console.error("Please set one of REFLECT_API_TOKEN, INSIGHT_HUB_AUTH_TOKEN, API_HUB_API_KEY or PACT_BROKER_BASE_URL / (and relevant Pact auth) environment variables");
|
|
54
|
+
process.exit(1);
|
|
46
55
|
}
|
|
47
56
|
const transport = new StdioServerTransport();
|
|
48
57
|
await server.connect(transport);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BaseAPI, pickFieldsFromArray } from './base.js';
|
|
2
2
|
// --- API Class ---
|
|
3
3
|
export class CurrentUserAPI extends BaseAPI {
|
|
4
|
-
static organizationFields = ['id', 'name'];
|
|
4
|
+
static organizationFields = ['id', 'name', 'slug'];
|
|
5
5
|
static projectFields = ['id', 'name', 'slug', 'api_key'];
|
|
6
6
|
constructor(configuration) {
|
|
7
7
|
super(configuration);
|
|
@@ -25,7 +25,10 @@ export class CurrentUserAPI extends BaseAPI {
|
|
|
25
25
|
url,
|
|
26
26
|
}, paginate);
|
|
27
27
|
// Only return allowed fields
|
|
28
|
-
return
|
|
28
|
+
return {
|
|
29
|
+
...data,
|
|
30
|
+
body: pickFieldsFromArray(data.body || [], CurrentUserAPI.organizationFields)
|
|
31
|
+
};
|
|
29
32
|
}
|
|
30
33
|
/**
|
|
31
34
|
* List projects for a given organization
|
|
@@ -49,6 +52,9 @@ export class CurrentUserAPI extends BaseAPI {
|
|
|
49
52
|
url,
|
|
50
53
|
}, paginate);
|
|
51
54
|
// Only return allowed fields
|
|
52
|
-
return
|
|
55
|
+
return {
|
|
56
|
+
...data,
|
|
57
|
+
body: pickFieldsFromArray(data.body || [], CurrentUserAPI.projectFields)
|
|
58
|
+
};
|
|
53
59
|
}
|
|
54
60
|
}
|