@smartbear/mcp 0.24.0 → 0.25.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.
Files changed (137) hide show
  1. package/dist/bearq/client.js +12 -13
  2. package/dist/bearq/tool/tasks/chat-with-qa-lead.js +1 -0
  3. package/dist/bearq/tool/tasks/expand-application-model.js +1 -0
  4. package/dist/bearq/tool/tasks/get-task-status.js +1 -0
  5. package/dist/bearq/tool/tasks/get-task.js +1 -0
  6. package/dist/bearq/tool/tasks/refine-all-draft-tests.js +1 -0
  7. package/dist/bearq/tool/tasks/refine-test-cases.js +1 -0
  8. package/dist/bearq/tool/tasks/refine-tests-in-functional-areas.js +1 -0
  9. package/dist/bearq/tool/tasks/run-regression-tests.js +1 -0
  10. package/dist/bearq/tool/tasks/run-test-cases.js +1 -0
  11. package/dist/bearq/tool/tasks/run-tests-in-functional-areas.js +1 -0
  12. package/dist/bearq/tool/tasks/stop-task.js +1 -0
  13. package/dist/bearq/tool/tasks/wait-for-task.js +1 -0
  14. package/dist/bugsnag/client.js +24 -44
  15. package/dist/bugsnag/tool/error/get-error.js +1 -0
  16. package/dist/bugsnag/tool/error/list-project-errors.js +1 -0
  17. package/dist/bugsnag/tool/error/update-error.js +1 -0
  18. package/dist/bugsnag/tool/event/get-event-details-from-dashboard-url.js +1 -0
  19. package/dist/bugsnag/tool/event/get-event.js +1 -0
  20. package/dist/bugsnag/tool/event/list-error-events.js +1 -0
  21. package/dist/bugsnag/tool/performance/get-network-endpoint-groupings.js +1 -0
  22. package/dist/bugsnag/tool/performance/get-span-group.js +1 -0
  23. package/dist/bugsnag/tool/performance/get-trace.js +1 -0
  24. package/dist/bugsnag/tool/performance/list-span-groups.js +1 -0
  25. package/dist/bugsnag/tool/performance/list-spans.js +1 -0
  26. package/dist/bugsnag/tool/performance/list-trace-fields.js +1 -0
  27. package/dist/bugsnag/tool/performance/set-network-endpoint-groupings.js +1 -0
  28. package/dist/bugsnag/tool/project/get-current-project.js +1 -0
  29. package/dist/bugsnag/tool/project/list-project-event-filters.js +1 -0
  30. package/dist/bugsnag/tool/project/list-projects.js +1 -0
  31. package/dist/bugsnag/tool/release/get-build.js +1 -0
  32. package/dist/bugsnag/tool/release/get-release.js +1 -0
  33. package/dist/bugsnag/tool/release/list-releases.js +1 -0
  34. package/dist/collaborator/client.js +24 -19
  35. package/dist/common/client-registry.js +63 -29
  36. package/dist/common/server.js +57 -1
  37. package/dist/common/transport-http.js +90 -69
  38. package/dist/common/transport-stdio.js +29 -24
  39. package/dist/package.json.js +1 -1
  40. package/dist/pactflow/client/tools.js +102 -0
  41. package/dist/pactflow/client.js +30 -43
  42. package/dist/qmetry/client/tools/automation-tools.js +2 -0
  43. package/dist/qmetry/client/tools/issue-tools.js +6 -0
  44. package/dist/qmetry/client/tools/project-tools.js +9 -0
  45. package/dist/qmetry/client/tools/requirement-tools.js +5 -0
  46. package/dist/qmetry/client/tools/testcase-tools.js +7 -0
  47. package/dist/qmetry/client/tools/testsuite-tools.js +11 -0
  48. package/dist/qmetry/client.js +20 -18
  49. package/dist/qtm4j/client.js +42 -32
  50. package/dist/qtm4j/config/constants.js +101 -1
  51. package/dist/qtm4j/config/field-resolution.types.js +3 -1
  52. package/dist/qtm4j/http/api-client.js +20 -2
  53. package/dist/qtm4j/resolver/resolver-registry.js +7 -1
  54. package/dist/qtm4j/resolver/resolvers/requirement-id-resolver.js +28 -0
  55. package/dist/qtm4j/resolver/resolvers/test-cycle-uid-resolver.js +28 -0
  56. package/dist/qtm4j/schema/linked-items.schema.js +95 -0
  57. package/dist/qtm4j/schema/requirements.schema.js +109 -0
  58. package/dist/qtm4j/schema/test-cycle.link.schema.js +260 -0
  59. package/dist/qtm4j/tool/project/get-projects.js +2 -1
  60. package/dist/qtm4j/tool/project/set-project-context.js +2 -1
  61. package/dist/qtm4j/tool/requirement/get-linked-testcases.js +93 -0
  62. package/dist/qtm4j/tool/requirement/link-testcases.js +107 -0
  63. package/dist/qtm4j/tool/requirement/unlink-testcases.js +97 -0
  64. package/dist/qtm4j/tool/test-automation/get-automation-history.js +2 -1
  65. package/dist/qtm4j/tool/test-automation/upload-automation-result.js +2 -1
  66. package/dist/qtm4j/tool/test-case/create-test-case.js +2 -1
  67. package/dist/qtm4j/tool/test-case/get-linked-requirements.js +67 -0
  68. package/dist/qtm4j/tool/test-case/get-test-cases.js +2 -1
  69. package/dist/qtm4j/tool/test-case/get-test-steps.js +2 -1
  70. package/dist/qtm4j/tool/test-case/link-requirements.js +124 -0
  71. package/dist/qtm4j/tool/test-case/unlink-requirements.js +116 -0
  72. package/dist/qtm4j/tool/test-case/update-test-case.js +2 -1
  73. package/dist/qtm4j/tool/test-cycle/create-test-cycle.js +2 -1
  74. package/dist/qtm4j/tool/test-cycle/get-linked-requirements.js +71 -0
  75. package/dist/qtm4j/tool/test-cycle/link-requirements.js +91 -0
  76. package/dist/qtm4j/tool/test-cycle/link-testcases.js +118 -0
  77. package/dist/qtm4j/tool/test-cycle/search-linked-testcases.js +114 -0
  78. package/dist/qtm4j/tool/test-cycle/search-test-cycle.js +2 -1
  79. package/dist/qtm4j/tool/test-cycle/unlink-requirements.js +87 -0
  80. package/dist/qtm4j/tool/test-cycle/unlink-testcases.js +103 -0
  81. package/dist/qtm4j/tool/test-cycle/update-test-cycle.js +2 -1
  82. package/dist/reflect/client.js +15 -24
  83. package/dist/reflect/config/constants.js +0 -2
  84. package/dist/reflect/tool/recording/add-prompt-step.js +1 -0
  85. package/dist/reflect/tool/recording/add-segment.js +1 -0
  86. package/dist/reflect/tool/recording/connect-to-session.js +1 -0
  87. package/dist/reflect/tool/recording/delete-previous-step.js +1 -0
  88. package/dist/reflect/tool/recording/get-screenshot.js +1 -0
  89. package/dist/reflect/tool/suites/cancel-suite-execution.js +1 -0
  90. package/dist/reflect/tool/suites/execute-suite.js +1 -0
  91. package/dist/reflect/tool/suites/get-suite-execution-status.js +1 -0
  92. package/dist/reflect/tool/suites/list-suite-executions.js +1 -0
  93. package/dist/reflect/tool/suites/list-suites.js +1 -0
  94. package/dist/reflect/tool/tests/get-test-status.js +1 -0
  95. package/dist/reflect/tool/tests/list-segments.js +1 -0
  96. package/dist/reflect/tool/tests/list-tests.js +1 -0
  97. package/dist/reflect/tool/tests/run-test.js +1 -0
  98. package/dist/swagger/client/tools.js +23 -0
  99. package/dist/swagger/client.js +25 -28
  100. package/dist/zephyr/client.js +14 -21
  101. package/dist/zephyr/tool/environment/get-environments.js +1 -0
  102. package/dist/zephyr/tool/folder/create-folder.js +1 -0
  103. package/dist/zephyr/tool/issue-link/get-test-cases.js +1 -0
  104. package/dist/zephyr/tool/issue-link/get-test-cycles.js +1 -0
  105. package/dist/zephyr/tool/issue-link/get-test-executions.js +1 -0
  106. package/dist/zephyr/tool/priority/get-priorities.js +1 -0
  107. package/dist/zephyr/tool/project/get-project.js +1 -0
  108. package/dist/zephyr/tool/project/get-projects.js +1 -0
  109. package/dist/zephyr/tool/status/get-statuses.js +1 -0
  110. package/dist/zephyr/tool/test-case/create-issue-link.js +1 -0
  111. package/dist/zephyr/tool/test-case/create-test-case.js +1 -0
  112. package/dist/zephyr/tool/test-case/create-test-script.js +1 -0
  113. package/dist/zephyr/tool/test-case/create-test-steps.js +1 -0
  114. package/dist/zephyr/tool/test-case/create-web-link.js +1 -0
  115. package/dist/zephyr/tool/test-case/get-links.js +1 -0
  116. package/dist/zephyr/tool/test-case/get-test-case.js +1 -0
  117. package/dist/zephyr/tool/test-case/get-test-cases.js +1 -0
  118. package/dist/zephyr/tool/test-case/get-test-script.js +1 -0
  119. package/dist/zephyr/tool/test-case/get-test-steps.js +1 -0
  120. package/dist/zephyr/tool/test-case/update-test-case.js +1 -0
  121. package/dist/zephyr/tool/test-cycle/create-issue-link.js +1 -0
  122. package/dist/zephyr/tool/test-cycle/create-test-cycle.js +1 -0
  123. package/dist/zephyr/tool/test-cycle/create-web-link.js +1 -0
  124. package/dist/zephyr/tool/test-cycle/get-links.js +1 -0
  125. package/dist/zephyr/tool/test-cycle/get-test-cycle.js +1 -0
  126. package/dist/zephyr/tool/test-cycle/get-test-cycles.js +1 -0
  127. package/dist/zephyr/tool/test-cycle/update-test-cycle.js +1 -0
  128. package/dist/zephyr/tool/test-execution/create-issue-link.js +1 -0
  129. package/dist/zephyr/tool/test-execution/create-test-execution.js +1 -0
  130. package/dist/zephyr/tool/test-execution/get-test-execution-links.js +1 -0
  131. package/dist/zephyr/tool/test-execution/get-test-execution.js +1 -0
  132. package/dist/zephyr/tool/test-execution/get-test-executions.js +1 -0
  133. package/dist/zephyr/tool/test-execution/get-test-steps.js +1 -0
  134. package/dist/zephyr/tool/test-execution/update-test-execution.js +1 -0
  135. package/dist/zephyr/tool/test-execution/update-test-steps.js +1 -0
  136. package/package.json +1 -1
  137. package/dist/common/request-context.js +0 -20
@@ -1,28 +1,36 @@
1
- import { ZodURL } from "zod";
1
+ import { ZodURL, ZodError } from "zod";
2
2
  import { fullyUnwrapZodType, isOptionalType } from "./zod-utils.js";
3
3
  class ClientRegistry {
4
4
  entries = [];
5
- enabledClients = null;
5
+ enabledClients;
6
6
  /**
7
- * Configure which clients should be enabled based on MCP_CLIENTS env var
7
+ * Configure which clients should be enabled based on MCP_CLIENTS env var and MCP_TOOLSETS (to enable any referenced clients)
8
8
  * If not set or empty, all clients are enabled
9
9
  * If set, should be comma-separated list of client names (case-insensitive)
10
10
  */
11
11
  constructor() {
12
- const enabledClientsEnv = process.env.MCP_CLIENTS?.trim();
13
- if (!enabledClientsEnv) {
14
- this.enabledClients = null;
15
- return;
12
+ let enabledClientsStr = "";
13
+ if (process.env.MCP_CLIENTS) {
14
+ enabledClientsStr = process.env.MCP_CLIENTS.trim();
15
+ }
16
+ enabledClientsStr += ",";
17
+ if (process.env.MCP_TOOLSETS) {
18
+ enabledClientsStr += process.env.MCP_TOOLSETS.trim();
16
19
  }
17
20
  this.enabledClients = new Set(
18
- enabledClientsEnv.split(",").map((name) => name.trim().toLowerCase()).filter((name) => name.length > 0)
21
+ enabledClientsStr.trim().split(",").map((name) => {
22
+ if (name.includes(":")) {
23
+ return name.split(":")[0].trim().toLowerCase();
24
+ }
25
+ return name.trim().toLowerCase();
26
+ }).filter((name) => name.length > 0)
19
27
  );
20
28
  }
21
29
  /**
22
- * Check if a client is enabled based on MCP_CLIENTS configuration
30
+ * Check if a client is enabled based on client filtering configuration
23
31
  */
24
32
  isClientEnabled(name) {
25
- if (this.enabledClients === null) {
33
+ if (this.enabledClients.size === 0) {
26
34
  return true;
27
35
  }
28
36
  return this.enabledClients.has(name.toLowerCase());
@@ -76,31 +84,57 @@ class ClientRegistry {
76
84
  return this.entries.filter((entry) => this.isClientEnabled(entry.name));
77
85
  }
78
86
  /**
79
- * Configures all enabled clients on the given MCP server
87
+ * Registers all enabled clients on the given MCP server
80
88
  * @param server The MCP server on which the client is registered
81
89
  * @param getConfigValue A function that obtains a configuration value for the given client and requirement name
82
- * @returns The number of clients successfully configured
90
+ * @returns The number of clients successfully added
83
91
  */
84
- async configure(server, getConfigValue, ignoreMissingRequiredConfigs = false) {
85
- let configuredCount = 0;
86
- entryLoop: for (const entry of this.getAll()) {
87
- const config = {};
88
- for (const configKey of Object.keys(entry.config.shape)) {
89
- const value = getConfigValue(entry, configKey);
90
- if (value !== null) {
91
- this.validateAllowedEndpoint(entry.config.shape[configKey], value);
92
- config[configKey] = value;
93
- } else if (!ignoreMissingRequiredConfigs && !isOptionalType(entry.config.shape[configKey])) {
94
- continue entryLoop;
92
+ async registerAll(server, getConfigValue, configure, authorizationCheck) {
93
+ if (authorizationCheck && !configure) {
94
+ throw new Error(
95
+ "Cannot perform authorization check without configuring clients"
96
+ );
97
+ }
98
+ let addedCount = 0;
99
+ clientLoop: for (const client of this.getAll()) {
100
+ if (configure) {
101
+ const config = {};
102
+ for (const configKey of Object.keys(client.config.shape)) {
103
+ const value = getConfigValue(configKey, client);
104
+ if (value) {
105
+ this.validateAllowedEndpoint(client.config.shape[configKey], value);
106
+ config[configKey] = value;
107
+ } else if (!isOptionalType(client.config.shape[configKey])) {
108
+ continue clientLoop;
109
+ }
110
+ }
111
+ let parsedConfig;
112
+ try {
113
+ parsedConfig = client.config.parse(config);
114
+ } catch (error) {
115
+ if (error instanceof ZodError) {
116
+ console.warn(
117
+ `Configuration for client ${client.name} is invalid: ${error.issues.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ")}`
118
+ );
119
+ } else {
120
+ console.warn(
121
+ `Unable to apply configuration for client ${client.name}: ${error}`
122
+ );
123
+ }
124
+ continue;
125
+ }
126
+ await client.configure(server, parsedConfig);
127
+ if (!client.isConfigured()) {
128
+ continue;
129
+ }
130
+ if (authorizationCheck && !client.hasAuth()) {
131
+ continue;
95
132
  }
96
133
  }
97
- await entry.configure(server, config);
98
- if (entry.isConfigured()) {
99
- await server.addClient(entry);
100
- configuredCount++;
101
- }
134
+ await server.addClient(client);
135
+ addedCount++;
102
136
  }
103
- return configuredCount;
137
+ return addedCount;
104
138
  }
105
139
  /**
106
140
  * Clear all registrations (useful for testing)
@@ -11,7 +11,9 @@ class SmartBearMcpServer extends McpServer {
11
11
  samplingSupported = false;
12
12
  elicitationSupported = false;
13
13
  clients = [];
14
- constructor() {
14
+ enabledToolsets;
15
+ getEnvFn;
16
+ constructor(getEnvFn, enabledToolsets) {
15
17
  super(
16
18
  {
17
19
  name: MCP_SERVER_NAME,
@@ -27,11 +29,26 @@ class SmartBearMcpServer extends McpServer {
27
29
  }
28
30
  }
29
31
  );
32
+ this.getEnvFn = getEnvFn;
30
33
  this.cache = new CacheService();
34
+ if (enabledToolsets) {
35
+ this.enabledToolsets = enabledToolsets.split(",").map((s) => s.trim().toLowerCase());
36
+ }
31
37
  }
32
38
  getCache() {
33
39
  return this.cache;
34
40
  }
41
+ /**
42
+ * Makes the server's getEnv function available to clients, validating that it is defined in the client's authentication fields if a client is provided
43
+ */
44
+ getEnv = (key, client) => {
45
+ if (client && !Object.keys(client.authenticationFields.shape).includes(key)) {
46
+ throw new Error(
47
+ `Environment variable "${key}" is not defined in the ${client.name} client's authentication schema.`
48
+ );
49
+ }
50
+ return this.getEnvFn(key, client);
51
+ };
35
52
  setSamplingSupported(supported) {
36
53
  this.samplingSupported = supported;
37
54
  }
@@ -56,6 +73,9 @@ class SmartBearMcpServer extends McpServer {
56
73
  this.clients.push(client);
57
74
  await client.registerTools(
58
75
  (params, cb) => {
76
+ if (!this.isToolEnabled(client, params.toolset)) {
77
+ return null;
78
+ }
59
79
  const toolName = this.getCapabilityName(client, params.title);
60
80
  const toolTitle = this.getCapabilityTitle(client, params.title);
61
81
  if (toolName.length > 64) {
@@ -236,9 +256,40 @@ ${result.instructions}`
236
256
  getCapabilityName(client, title) {
237
257
  return `${client.capabilityPrefix}_${title.replace(/\s+/g, "_").toLowerCase()}`;
238
258
  }
259
+ /**
260
+ * The tool is enabled if:
261
+ * - No enabled toolsets are defined on the server, or
262
+ * - The client is included in the enabled toolsets list, or
263
+ * - The toolset is included in the enabled toolsets list, or
264
+ * - The toolset is in the client's default list and there is at least one specific toolset enabled for the client
265
+ * @param client
266
+ * @param toolset
267
+ * @returns whether to register the tool based on enabled toolsets configuration
268
+ */
269
+ isToolEnabled(client, toolset) {
270
+ if (!this.enabledToolsets) {
271
+ return true;
272
+ }
273
+ const clientPrefix = client.configPrefix.toLowerCase();
274
+ const clientIsEnabled = this.enabledToolsets.some(
275
+ (ts) => !ts.includes(":") && ts === clientPrefix
276
+ );
277
+ if (clientIsEnabled) {
278
+ return true;
279
+ }
280
+ const toolsetEntries = this.enabledToolsets.filter(
281
+ (ts) => ts.includes(":") && ts.split(":")[0] === clientPrefix
282
+ );
283
+ if (toolsetEntries.length === 0) {
284
+ return false;
285
+ }
286
+ const toolsetName = `${clientPrefix}:${toolset.replace(/[\s\-_]/g, "")}`.toLowerCase();
287
+ return toolsetEntries.includes(toolsetName) || (client.defaultToolsets || [])?.includes(toolset);
288
+ }
239
289
  getDescription(params) {
240
290
  const {
241
291
  summary,
292
+ toolset,
242
293
  useCases,
243
294
  examples,
244
295
  inputSchema,
@@ -246,6 +297,11 @@ ${result.instructions}`
246
297
  outputDescription
247
298
  } = params;
248
299
  let description = summary;
300
+ if (toolset) {
301
+ description += `
302
+
303
+ **Toolset:** ${toolset}`;
304
+ }
249
305
  if (inputSchema && inputSchema instanceof ZodObject) {
250
306
  let parameters = Object.keys(inputSchema.shape).map((key) => {
251
307
  const field = inputSchema.shape[key];
@@ -5,11 +5,12 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
5
5
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
6
6
  import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
7
7
  import { clientRegistry } from "./client-registry.js";
8
- import { withRequestContext } from "./request-context.js";
9
8
  import { SmartBearMcpServer } from "./server.js";
10
9
  import { isDraining, registerShutdownHandler } from "./shutdown.js";
11
10
  import { getEnvVarName } from "./transport-stdio.js";
12
- import { isOptionalType, getTypeDescription } from "./zod-utils.js";
11
+ import { getTypeDescription, isOptionalType } from "./zod-utils.js";
12
+ class AuthorizationError extends Error {
13
+ }
13
14
  const PROBE_HEADERS = {
14
15
  "Content-Type": "application/json",
15
16
  "Cache-Control": "no-store"
@@ -280,10 +281,7 @@ async function handleStreamableHttpRequest(req, res, transports) {
280
281
  );
281
282
  return;
282
283
  }
283
- await withRequestContext(
284
- req,
285
- async () => await transport.handleRequest(req, res, parsedBody)
286
- );
284
+ await transport.handleRequest(req, res, parsedBody);
287
285
  } catch (error) {
288
286
  console.error("Error handling StreamableHTTP request:", error);
289
287
  res.writeHead(500, { "Content-Type": "text/plain" });
@@ -328,13 +326,10 @@ async function handleLegacyMessageRequest(req, res, url, transports) {
328
326
  req.on("end", async () => {
329
327
  try {
330
328
  const parsedBody = JSON.parse(body);
331
- await withRequestContext(
329
+ await session.transport.handlePostMessage(
332
330
  req,
333
- async () => await session.transport.handlePostMessage(
334
- req,
335
- res,
336
- parsedBody
337
- )
331
+ res,
332
+ parsedBody
338
333
  );
339
334
  } catch (error) {
340
335
  console.error("Error handling POST message:", error);
@@ -343,93 +338,118 @@ async function handleLegacyMessageRequest(req, res, url, transports) {
343
338
  }
344
339
  });
345
340
  }
341
+ function resolveFromRequest(req, key, prefix) {
342
+ const queryStringName = getQueryStringName(key, prefix);
343
+ const queryParams = querystring.parse(req.url?.split("?")[1] || "");
344
+ let value = queryParams[queryStringName] || queryParams[queryStringName.toLowerCase()];
345
+ if (typeof value === "string") {
346
+ return value;
347
+ }
348
+ const headerName = getHeaderName(key, prefix);
349
+ value = req.headers[headerName] || req.headers[headerName.toLowerCase()];
350
+ if (typeof value === "string") {
351
+ if (value.toLowerCase().startsWith("bearer ")) {
352
+ value = value.slice("bearer ".length);
353
+ } else if (value.toLowerCase().startsWith("token ")) {
354
+ value = value.slice("token ".length);
355
+ } else if (value.toLowerCase().startsWith("basic ")) {
356
+ value = value.slice("basic ".length);
357
+ }
358
+ return value;
359
+ }
360
+ const envVarName = getEnvVarName(key, prefix);
361
+ return process.env[envVarName];
362
+ }
363
+ function makeConfigFn(req) {
364
+ return (key, client) => resolveFromRequest(req, key, client?.configPrefix);
365
+ }
346
366
  async function newServer(req, res) {
347
- const server = new SmartBearMcpServer();
367
+ const configFn = makeConfigFn(req);
368
+ const enabledToolsets = resolveFromRequest(req, "toolsets", "smartbear") || void 0;
369
+ const server = new SmartBearMcpServer(configFn, enabledToolsets);
348
370
  try {
349
- const configuredCount = await withRequestContext(
350
- req,
351
- () => clientRegistry.configure(
352
- server,
353
- (client, key) => {
354
- const queryStringName = getQueryStringName(client, key);
355
- const queryParams = querystring.parse(req.url?.split("?")[1] || "");
356
- let value = queryParams[queryStringName] || queryParams[queryStringName.toLowerCase()];
357
- if (typeof value === "string") {
358
- return value;
359
- }
360
- const headerName = getHeaderName(client, key);
361
- value = req.headers[headerName] || req.headers[headerName.toLowerCase()];
362
- if (typeof value === "string") {
363
- return value;
364
- }
365
- const envVarName = getEnvVarName(client, key);
366
- return process.env[envVarName] || null;
367
- },
368
- true
369
- // ignoreMissingRequiredConfigs
370
- )
371
- );
372
- console.log(
373
- `Configured ${configuredCount} clients for new server instance`
371
+ const configuredCount = await clientRegistry.registerAll(
372
+ server,
373
+ configFn,
374
+ true,
375
+ false
374
376
  );
375
377
  if (configuredCount === 0) {
376
378
  throw new Error(
377
- "No clients successfully configured. Missing authentication headers."
379
+ "No clients successfully configured. The request headers are missing the required configuration."
378
380
  );
379
381
  }
380
- const hasAuth = withRequestContext(
381
- req,
382
- () => server.getClients().some((client) => {
383
- if (!client.getAuthToken) return true;
384
- return client.getAuthToken() !== null;
385
- })
382
+ console.log(
383
+ `Configured ${configuredCount} clients for new server instance`
386
384
  );
387
- if (!hasAuth) {
388
- throw new Error(
385
+ const hasNoAuth = !server.getClients().some((client) => client.hasAuth());
386
+ if (hasNoAuth) {
387
+ throw new AuthorizationError(
389
388
  "No clients have valid authentication credentials. Please authenticate via OAuth or provide alternative auth headers (e.g. API key or personal auth token)."
390
389
  );
391
390
  }
391
+ return server;
392
392
  } catch (error) {
393
- const headerHelp = getHttpHeadersHelp();
394
- const errorMessage = headerHelp.length > 0 ? `Configuration error: ${error instanceof Error ? error.message : String(error)}. Please provide valid headers:
395
- ${headerHelp.join("\n")}` : "No clients support HTTP header configuration.";
396
393
  const headers = {
397
394
  "Content-Type": "text/plain"
398
395
  };
399
- if (req.headers.host) {
400
- headers["WWW-Authenticate"] = `OAuth resource_metadata="http://${req.headers.host}/.well-known/oauth-protected-resource"`;
396
+ if (error instanceof AuthorizationError) {
397
+ if (req.headers.host) {
398
+ headers["WWW-Authenticate"] = `OAuth resource_metadata="http://${req.headers.host}/.well-known/oauth-protected-resource"`;
399
+ }
400
+ res.writeHead(401, headers);
401
+ res.end(error.message);
402
+ } else {
403
+ const headerHelp = getHttpHeadersHelp();
404
+ let errorMessage = `Configuration error: ${error instanceof Error ? error.message : String(error)}.`;
405
+ if (headerHelp.length > 0) {
406
+ errorMessage += ` Please provide valid headers:
407
+ ${headerHelp.join("\n")}`;
408
+ }
409
+ res.writeHead(500, headers);
410
+ res.end(errorMessage);
401
411
  }
402
- res.writeHead(401, headers);
403
- res.end(errorMessage);
404
412
  return null;
405
413
  }
406
- return server;
407
414
  }
408
- function getHeaderName(client, key) {
409
- return `${client.configPrefix}-${key.split("_").map(
415
+ function getHeaderName(key, clientPrefix) {
416
+ const prefix = `${clientPrefix ? `${clientPrefix}-${key}` : key}`;
417
+ return prefix.split(/[\s\-_]/).map(
410
418
  (part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()
411
- ).join("-")}`;
419
+ ).join("-");
412
420
  }
413
- function getQueryStringName(client, key) {
414
- return `${client.configPrefix.toLowerCase()}${key.split("_").map(
415
- (part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()
416
- ).join("")}`;
421
+ function getQueryStringName(key, clientPrefix) {
422
+ const prefix = `${clientPrefix ? `${clientPrefix}-${key}` : key}`;
423
+ return prefix.split(/[\s\-_]/).map(
424
+ (part, i) => i === 0 ? part.toLowerCase() : part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()
425
+ ).join("");
417
426
  }
418
427
  function getHttpHeaders() {
419
428
  const headers = /* @__PURE__ */ new Set();
420
- for (const entry of clientRegistry.getAll()) {
421
- for (const configKey of Object.keys(entry.config.shape)) {
422
- headers.add(getHeaderName(entry, configKey));
429
+ for (const client of clientRegistry.getAll()) {
430
+ for (const key of [
431
+ ...Object.keys(client.config.shape),
432
+ ...Object.keys(client.authenticationFields.shape)
433
+ ]) {
434
+ headers.add(getHeaderName(key, client.configPrefix));
423
435
  }
424
436
  }
425
437
  return Array.from(headers).sort((a, b) => a.localeCompare(b));
426
438
  }
427
439
  function getHttpHeadersHelp() {
428
440
  const messages = [];
429
- for (const entry of clientRegistry.getAll()) {
430
- messages.push(` - ${entry.name}:`);
431
- for (const [configKey, requirement] of Object.entries(entry.config.shape)) {
432
- const headerName = getHeaderName(entry, configKey);
441
+ for (const client of clientRegistry.getAll()) {
442
+ messages.push(` - ${client.name}:`);
443
+ for (const [authKey, requirement] of Object.entries(
444
+ client.authenticationFields.shape
445
+ )) {
446
+ const headerName = getHeaderName(authKey, client.configPrefix);
447
+ messages.push(` - ${headerName}: ${getTypeDescription(requirement)}`);
448
+ }
449
+ for (const [configKey, requirement] of Object.entries(
450
+ client.config.shape
451
+ )) {
452
+ const headerName = getHeaderName(configKey, client.configPrefix);
433
453
  const requiredTag = isOptionalType(requirement) ? " (optional)" : " (required)";
434
454
  messages.push(
435
455
  ` - ${headerName}${requiredTag}: ${getTypeDescription(requirement)}`
@@ -439,6 +459,7 @@ function getHttpHeadersHelp() {
439
459
  return messages;
440
460
  }
441
461
  export {
462
+ AuthorizationError,
442
463
  drainHttpTransport,
443
464
  getBaseUrl,
444
465
  getHeaderName,
@@ -1,18 +1,20 @@
1
+ import { enableCompileCache } from "node:module";
1
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
3
  import { clientRegistry } from "./client-registry.js";
3
4
  import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "./info.js";
4
5
  import { SmartBearMcpServer } from "./server.js";
5
6
  import { registerShutdownHandler } from "./shutdown.js";
6
- import { isOptionalType, getTypeDescription } from "./zod-utils.js";
7
- function getNoConfigMessage() {
7
+ import { getTypeDescription } from "./zod-utils.js";
8
+ function getConfigMessage() {
8
9
  const messages = [];
9
- for (const entry of clientRegistry.getAll()) {
10
- messages.push(` - ${entry.name}:`);
11
- for (const [configKey, requirement] of Object.entries(entry.config.shape)) {
12
- const envVarName = getEnvVarName(entry, configKey);
13
- const requiredTag = isOptionalType(requirement) ? " (optional)" : " (required)";
10
+ for (const client of clientRegistry.getAll()) {
11
+ messages.push(` - ${client.name}:`);
12
+ for (const [configKey, requirement] of [
13
+ ...Object.entries(client.authenticationFields.shape),
14
+ ...Object.entries(client.config.shape)
15
+ ]) {
14
16
  messages.push(
15
- ` - ${envVarName}${requiredTag}: ${getTypeDescription(requirement)}`
17
+ ` - ${getEnvVarName(configKey, client.configPrefix)}: ${getTypeDescription(requirement)}`
16
18
  );
17
19
  }
18
20
  }
@@ -26,26 +28,28 @@ async function runStdioMode() {
26
28
  console.log(
27
29
  "The following environment variables can be set to configure each of the SmartBear clients:"
28
30
  );
29
- console.log(getNoConfigMessage().join("\n"));
31
+ console.log(getConfigMessage().join("\n"));
30
32
  process.exit(0);
31
33
  }
32
- const server = new SmartBearMcpServer();
33
- const configuredCount = await clientRegistry.configure(
34
+ enableCompileCache();
35
+ const configFn = (key, client) => {
36
+ const envVarName = getEnvVarName(key, client?.configPrefix);
37
+ return process.env[envVarName];
38
+ };
39
+ const server = new SmartBearMcpServer(configFn, process.env.MCP_TOOLSETS);
40
+ const addedCount = await clientRegistry.registerAll(
34
41
  server,
35
- (client, key) => {
36
- const envVarName = getEnvVarName(client, key);
37
- return process.env[envVarName] || null;
38
- }
42
+ configFn,
43
+ true,
44
+ true
39
45
  );
40
- if (configuredCount === 0) {
41
- const message = getNoConfigMessage();
46
+ if (addedCount === 0) {
47
+ const message = getConfigMessage();
42
48
  console.warn(
43
- message.length > 0 ? `No clients configured. Please provide valid environment variables for at least one client:
44
- ${message.join("\n")}` : "No clients support environment variable configuration."
49
+ `No clients configured. Please provide valid environment variables for at least one client:
50
+ ${message.join("\n")}`
45
51
  );
46
- for (const entry of clientRegistry.getAll()) {
47
- await server.addClient(entry);
48
- }
52
+ await clientRegistry.registerAll(server, configFn, false, false);
49
53
  }
50
54
  const transport = new StdioServerTransport();
51
55
  registerShutdownHandler("stdio-transport", async () => {
@@ -70,8 +74,9 @@ ${message.join("\n")}` : "No clients support environment variable configuration.
70
74
  };
71
75
  await server.connect(transport);
72
76
  }
73
- function getEnvVarName(client, key) {
74
- return `${client.configPrefix.toUpperCase().replace(/-/g, "_")}_${key.toUpperCase()}`;
77
+ function getEnvVarName(key, clientPrefix) {
78
+ const prefix = `${clientPrefix ? `${clientPrefix}-${key}` : key}`.toUpperCase();
79
+ return prefix.replace(/[\s\-_]/g, "_");
75
80
  }
76
81
  export {
77
82
  getEnvVarName,
@@ -1,4 +1,4 @@
1
- const version = "0.24.0";
1
+ const version = "0.25.0";
2
2
  const config = { "mcpServerName": "SmartBear MCP Server" };
3
3
  const packageJson = {
4
4
  version,