@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.
@@ -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 McpServer({
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
- if (!reflectToken && !insightHubToken && !apiHubToken) {
29
- console.error("Please set one of REFLECT_API_TOKEN, INSIGHT_HUB_AUTH_TOKEN or API_HUB_API_KEY environment variables");
30
- process.exit(1);
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
- const reflectClient = new ReflectClient(reflectToken);
34
- reflectClient.registerTools(server);
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
- insightHubClient.registerTools(server);
41
- insightHubClient.registerResources(server);
32
+ server.addClient(insightHubClient);
33
+ client_defined = true;
42
34
  }
43
35
  if (apiHubToken) {
44
- const apiHubClient = new ApiHubClient(apiHubToken);
45
- apiHubClient.registerTools(server);
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);