@smartbear/mcp 0.3.0 → 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 +68 -7
- 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.js +383 -385
- package/dist/package.json +3 -2
- 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/client.test.js +114 -182
- 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/package.json +3 -2
- package/dist/common/templates.js +0 -57
- package/dist/tests/unit/common/templates.test.js +0 -149
|
@@ -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);
|