@smartbear/mcp 0.27.2 → 0.29.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 CHANGED
@@ -35,7 +35,7 @@ See individual guides for suggested prompts and supported tools and resources:
35
35
  - [Portal](https://developer.smartbear.com/smartbear-mcp/docs/swagger-portal-integration) - Portal and product management capabilities
36
36
  - [Studio](https://developer.smartbear.com/smartbear-mcp/docs/swagger-studio-integration) - API and Domain management capabilities, including AI-powered API generation from prompts and automatic standardization
37
37
  - [Contract Testing (PactFlow)](https://developer.smartbear.com/pactflow/default/getting-started) - Contract testing capabilities
38
- - [Functional Testing](https://developer.smartbear.com/smartbear-mcp/docs/functional-testing-integration) - API test discovery capabilities
38
+ - [Functional Testing](https://developer.smartbear.com/smartbear-mcp/docs/swagger-functional-testing-integration) - API test discovery capabilities
39
39
  - [QMetry](https://developer.smartbear.com/smartbear-mcp/docs/qmetry-integration) - QMetry Test Management capabilities
40
40
  - [Zephyr](https://developer.smartbear.com/smartbear-mcp/docs/zephyr-integration) - Zephyr Test Management capabilities
41
41
  - [Collaborator](https://developer.smartbear.com/smartbear-mcp/docs/collaborator-integration) - Review and Remote System Configuration management capabilities
@@ -1,8 +1,9 @@
1
1
  import { z } from "zod";
2
- import { USER_AGENT } from "../common/info.js";
2
+ import { getUserAgent } from "../common/info.js";
3
3
  import { getRequestHeader } from "../common/request-context.js";
4
4
  import { ToolError } from "../common/tools.js";
5
5
  import { DEFAULT_API_BASE_URL, AUTHORIZATION_HEADER } from "./config/constants.js";
6
+ import { ListEnvironments } from "./tool/environments/list-environments.js";
6
7
  import { ChatWithQaLead } from "./tool/tasks/chat-with-qa-lead.js";
7
8
  import { ExpandApplicationModel } from "./tool/tasks/expand-application-model.js";
8
9
  import { GetTask } from "./tool/tasks/get-task.js";
@@ -53,7 +54,7 @@ class BearQClient {
53
54
  return {
54
55
  Authorization: `Bearer ${token}`,
55
56
  "Content-Type": "application/json",
56
- "User-Agent": USER_AGENT
57
+ "User-Agent": getUserAgent()
57
58
  };
58
59
  }
59
60
  async registerTools(register, _getInput) {
@@ -69,7 +70,8 @@ class BearQClient {
69
70
  new GetTask(this),
70
71
  new GetTaskStatus(this),
71
72
  new StopTask(this),
72
- new WaitForTask(this)
73
+ new WaitForTask(this),
74
+ new ListEnvironments(this)
73
75
  ];
74
76
  for (const tool of tools) {
75
77
  register(tool.specification, tool.handle);
@@ -0,0 +1,28 @@
1
+ import { z } from "zod";
2
+ import { Tool, ToolError } from "../../../common/tools.js";
3
+ const inputSchema = z.object({});
4
+ class ListEnvironments extends Tool {
5
+ specification = {
6
+ title: "List Environments",
7
+ toolset: "Environments",
8
+ summary: "Lists the environments configured in the workspace. Use this to discover valid environment names to pass to the test-running tools, and to identify the workspace default.",
9
+ inputSchema,
10
+ readOnly: true
11
+ };
12
+ handle = async (_args) => {
13
+ const res = await fetch(`${this.client.getBaseUrl()}/environments`, {
14
+ method: "GET",
15
+ headers: this.client.getHeaders()
16
+ });
17
+ if (!res.ok)
18
+ throw new ToolError(
19
+ `GET /environments failed: ${res.status} ${res.statusText}`
20
+ );
21
+ return {
22
+ content: [{ type: "text", text: JSON.stringify(await res.json()) }]
23
+ };
24
+ };
25
+ }
26
+ export {
27
+ ListEnvironments
28
+ };
@@ -1,6 +1,10 @@
1
1
  import { z } from "zod";
2
2
  import { Tool, ToolError } from "../../../common/tools.js";
3
- const inputSchema = z.object({});
3
+ const inputSchema = z.object({
4
+ environment: z.string().min(1).optional().describe(
5
+ "Target environment name to run tests against. Omit to use the workspace default."
6
+ )
7
+ });
4
8
  class RunRegressionTests extends Tool {
5
9
  specification = {
6
10
  title: "Run Regression Tests",
@@ -8,11 +12,14 @@ class RunRegressionTests extends Tool {
8
12
  summary: "Runs the full BearQ regression suite — every regression-ready test case in the workspace. Use for CI/CD or pre-release smoke.",
9
13
  inputSchema
10
14
  };
11
- handle = async (_args) => {
15
+ handle = async (args) => {
16
+ const { environment } = inputSchema.parse(args);
17
+ const body = { agent: "tester", mode: "run" };
18
+ if (environment !== void 0) body.environment = environment;
12
19
  const res = await fetch(`${this.client.getBaseUrl()}/tasks`, {
13
20
  method: "POST",
14
21
  headers: this.client.getHeaders(),
15
- body: JSON.stringify({ agent: "tester", mode: "run" })
22
+ body: JSON.stringify(body)
16
23
  });
17
24
  if (!res.ok)
18
25
  throw new ToolError(
@@ -1,7 +1,10 @@
1
1
  import { z } from "zod";
2
2
  import { Tool, ToolError } from "../../../common/tools.js";
3
3
  const inputSchema = z.object({
4
- testCaseIds: z.array(z.number().int().positive()).min(1).describe("IDs of BearQ regression test cases to run.")
4
+ testCaseIds: z.array(z.number().int().positive()).min(1).describe("IDs of BearQ regression test cases to run."),
5
+ environment: z.string().min(1).optional().describe(
6
+ "Target environment name to run tests against. Omit to use the workspace default."
7
+ )
5
8
  });
6
9
  class RunTestCases extends Tool {
7
10
  specification = {
@@ -11,11 +14,17 @@ class RunTestCases extends Tool {
11
14
  inputSchema
12
15
  };
13
16
  handle = async (args) => {
14
- const { testCaseIds } = inputSchema.parse(args);
17
+ const { testCaseIds, environment } = inputSchema.parse(args);
18
+ const body = {
19
+ agent: "tester",
20
+ mode: "run",
21
+ testCaseIds
22
+ };
23
+ if (environment !== void 0) body.environment = environment;
15
24
  const res = await fetch(`${this.client.getBaseUrl()}/tasks`, {
16
25
  method: "POST",
17
26
  headers: this.client.getHeaders(),
18
- body: JSON.stringify({ agent: "tester", mode: "run", testCaseIds })
27
+ body: JSON.stringify(body)
19
28
  });
20
29
  if (!res.ok)
21
30
  throw new ToolError(
@@ -1,7 +1,10 @@
1
1
  import { z } from "zod";
2
2
  import { Tool, ToolError } from "../../../common/tools.js";
3
3
  const inputSchema = z.object({
4
- functionalAreas: z.array(z.union([z.number().int().positive(), z.string().min(1)])).min(1).describe("Functional areas to target, by ID or name.")
4
+ functionalAreas: z.array(z.union([z.number().int().positive(), z.string().min(1)])).min(1).describe("Functional areas to target, by ID or name."),
5
+ environment: z.string().min(1).optional().describe(
6
+ "Target environment name to run tests against. Omit to use the workspace default."
7
+ )
5
8
  });
6
9
  class RunTestsInFunctionalAreas extends Tool {
7
10
  specification = {
@@ -11,11 +14,17 @@ class RunTestsInFunctionalAreas extends Tool {
11
14
  inputSchema
12
15
  };
13
16
  handle = async (args) => {
14
- const { functionalAreas } = inputSchema.parse(args);
17
+ const { functionalAreas, environment } = inputSchema.parse(args);
18
+ const body = {
19
+ agent: "tester",
20
+ mode: "run",
21
+ functionalAreas
22
+ };
23
+ if (environment !== void 0) body.environment = environment;
15
24
  const res = await fetch(`${this.client.getBaseUrl()}/tasks`, {
16
25
  method: "POST",
17
26
  headers: this.client.getHeaders(),
18
- body: JSON.stringify({ agent: "tester", mode: "run", functionalAreas })
27
+ body: JSON.stringify(body)
19
28
  });
20
29
  if (!res.ok)
21
30
  throw new ToolError(
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { USER_AGENT } from "../common/info.js";
2
+ import { getUserAgent } from "../common/info.js";
3
3
  import { getRequestHeader } from "../common/request-context.js";
4
4
  import { ToolError } from "../common/tools.js";
5
5
  import { CurrentUserAPI } from "./client/api/CurrentUser.js";
@@ -123,7 +123,7 @@ class BugsnagClient {
123
123
  );
124
124
  },
125
125
  headers: {
126
- "User-Agent": USER_AGENT,
126
+ "User-Agent": getUserAgent(),
127
127
  "Content-Type": "application/json",
128
128
  "X-Bugsnag-API": "true",
129
129
  "X-Version": "2"
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { USER_AGENT } from "../common/info.js";
2
+ import { getUserAgent } from "../common/info.js";
3
3
  import { getRequestHeader } from "../common/request-context.js";
4
4
  const ConfigurationSchema = z.object({
5
5
  base_url: z.url().describe("Collaborator server base URL"),
@@ -48,7 +48,10 @@ class CollaboratorClient {
48
48
  ];
49
49
  const response = await fetch(url, {
50
50
  method: "POST",
51
- headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
51
+ headers: {
52
+ "Content-Type": "application/json",
53
+ "User-Agent": getUserAgent()
54
+ },
52
55
  body: JSON.stringify(body)
53
56
  });
54
57
  if (!response.ok) {
@@ -0,0 +1,20 @@
1
+ import { getRequestContext } from "./request-context.js";
2
+ function toClientIdentity(info, protocolVersion) {
3
+ return {
4
+ name: info?.name,
5
+ version: info?.version,
6
+ protocolVersion
7
+ };
8
+ }
9
+ let processClientIdentity;
10
+ function setProcessClientIdentity(identity) {
11
+ processClientIdentity = identity;
12
+ }
13
+ function getCurrentClientIdentity() {
14
+ return getRequestContext()?.mcpClient ?? processClientIdentity;
15
+ }
16
+ export {
17
+ getCurrentClientIdentity,
18
+ setProcessClientIdentity,
19
+ toClientIdentity
20
+ };
@@ -1,11 +1,31 @@
1
1
  import packageJson from "../package.json.js";
2
+ import { getCurrentClientIdentity } from "./client-identity.js";
2
3
  const MCP_SERVER_NAME = packageJson.config.mcpServerName;
3
4
  const MCP_SERVER_VERSION = packageJson.version;
4
5
  const MCP_TRANSPORT = process.env.MCP_TRANSPORT?.toLowerCase().trim() || "stdio";
5
6
  const USER_AGENT = `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION} ${MCP_TRANSPORT}`;
7
+ function sanitizeUserAgentToken(value) {
8
+ return value.replace(/[()\r\n;]/g, "").trim();
9
+ }
10
+ function clientIdentitySuffix(identity) {
11
+ if (!identity?.name) {
12
+ return "";
13
+ }
14
+ const clientName = sanitizeUserAgentToken(identity.name) || "unknown";
15
+ const version = identity.version ? sanitizeUserAgentToken(identity.version) || "unknown" : "unknown";
16
+ return ` (client: ${clientName}; clientVersion: ${version})`;
17
+ }
18
+ function appendClientIdentity(baseUserAgent) {
19
+ return baseUserAgent + clientIdentitySuffix(getCurrentClientIdentity());
20
+ }
21
+ function getUserAgent() {
22
+ return appendClientIdentity(USER_AGENT);
23
+ }
6
24
  export {
7
25
  MCP_SERVER_NAME,
8
26
  MCP_SERVER_VERSION,
9
27
  MCP_TRANSPORT,
10
- USER_AGENT
28
+ USER_AGENT,
29
+ appendClientIdentity,
30
+ getUserAgent
11
31
  };
@@ -0,0 +1,29 @@
1
+ import { toClientIdentity, setProcessClientIdentity } from "./client-identity.js";
2
+ function handleInitializeMessage(server, message) {
3
+ if (typeof message !== "object" || message === null || !("method" in message) || message.method !== "initialize") {
4
+ return;
5
+ }
6
+ const params = message.params;
7
+ const identity = toClientIdentity(
8
+ params?.clientInfo,
9
+ params?.protocolVersion
10
+ );
11
+ server.setMcpClientIdentity(identity);
12
+ setProcessClientIdentity(identity);
13
+ const clientInfo = params?.clientInfo;
14
+ if (clientInfo) {
15
+ server.setClientInfo(clientInfo);
16
+ }
17
+ if (params?.protocolVersion === "2025-11-25") {
18
+ const clientCapabilities = params.capabilities ?? {};
19
+ if (Object.hasOwn(clientCapabilities, "sampling")) {
20
+ server.setSamplingSupported(true);
21
+ }
22
+ if (Object.hasOwn(clientCapabilities, "elicitation")) {
23
+ server.setElicitationSupported(true);
24
+ }
25
+ }
26
+ }
27
+ export {
28
+ handleInitializeMessage
29
+ };
@@ -6,6 +6,12 @@ function withRequestContext(req, fn) {
6
6
  function getRequestContext() {
7
7
  return requestContextStorage.getStore();
8
8
  }
9
+ function setRequestMcpClient(identity) {
10
+ const context = getRequestContext();
11
+ if (context) {
12
+ context.mcpClient = identity;
13
+ }
14
+ }
9
15
  function getRequestHeader(name) {
10
16
  const context = getRequestContext();
11
17
  if (!context?.headers) return void 0;
@@ -16,5 +22,6 @@ export {
16
22
  getRequestContext,
17
23
  getRequestHeader,
18
24
  requestContextStorage,
25
+ setRequestMcpClient,
19
26
  withRequestContext
20
27
  };
@@ -2,6 +2,7 @@ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mc
2
2
  import { ZodObject, ZodIntersection } from "zod";
3
3
  import Bugsnag from "./bugsnag.js";
4
4
  import { CacheService } from "./cache.js";
5
+ import { toClientIdentity } from "./client-identity.js";
5
6
  import { MCP_SERVER_VERSION, MCP_SERVER_NAME } from "./info.js";
6
7
  import { executeElicitationOrPolyfill, isElicitationPolyfillResult } from "./pollyfills.js";
7
8
  import { ToolError } from "./tools.js";
@@ -10,8 +11,10 @@ class SmartBearMcpServer extends McpServer {
10
11
  cache;
11
12
  samplingSupported = false;
12
13
  elicitationSupported = false;
14
+ clientInfo;
13
15
  clients = [];
14
16
  enabledToolsets;
17
+ mcpClientIdentity;
15
18
  constructor(enabledToolsets) {
16
19
  super(
17
20
  {
@@ -48,9 +51,42 @@ class SmartBearMcpServer extends McpServer {
48
51
  isElicitationSupported() {
49
52
  return this.elicitationSupported;
50
53
  }
54
+ setClientInfo(info) {
55
+ this.clientInfo = info;
56
+ }
57
+ getClientInfo() {
58
+ return this.clientInfo;
59
+ }
51
60
  getClients() {
52
61
  return this.clients;
53
62
  }
63
+ /**
64
+ * Record the MCP client identity reported in the `initialize` handshake.
65
+ * Captured once per session by the transport layer.
66
+ */
67
+ setMcpClientIdentity(identity) {
68
+ this.mcpClientIdentity = identity;
69
+ }
70
+ /**
71
+ * Return the MCP client identity for this session. Prefers the value captured
72
+ * at `initialize`; falls back to the SDK's `getClientVersion()` so callers
73
+ * still get an answer if the explicit capture was skipped.
74
+ */
75
+ getMcpClientIdentity() {
76
+ return this.mcpClientIdentity ?? toClientIdentity(this.server.getClientVersion());
77
+ }
78
+ /**
79
+ * Attach MCP client attribution to a Bugsnag event so errors can be segmented
80
+ * by originating client/marketplace.
81
+ */
82
+ addClientMetadata(event) {
83
+ const identity = this.getMcpClientIdentity();
84
+ event.addMetadata("mcpClient", {
85
+ mcp_client_name: identity.name ?? null,
86
+ mcp_client_version: identity.version ?? null,
87
+ mcp_protocol_version: identity.protocolVersion ?? null
88
+ });
89
+ }
54
90
  async cleanupSession(mcpSessionId) {
55
91
  for (const client of this.clients) {
56
92
  await client.cleanupSession?.(mcpSessionId);
@@ -106,6 +142,7 @@ class SmartBearMcpServer extends McpServer {
106
142
  } else {
107
143
  Bugsnag.notify(e, (event) => {
108
144
  event.addMetadata("app", { tool: toolName });
145
+ this.addClientMetadata(event);
109
146
  event.unhandled = true;
110
147
  });
111
148
  }
@@ -156,6 +193,7 @@ ${result.instructions}`
156
193
  resource: resourceName,
157
194
  url: url2
158
195
  });
196
+ this.addClientMetadata(event);
159
197
  event.unhandled = true;
160
198
  });
161
199
  throw e;
@@ -181,6 +219,7 @@ ${result.instructions}`
181
219
  event.addMetadata("app", {
182
220
  prompt: this.getCapabilityName(client, params.title)
183
221
  });
222
+ this.addClientMetadata(event);
184
223
  event.unhandled = true;
185
224
  });
186
225
  throw e;
@@ -5,7 +5,8 @@ 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";
8
+ import { handleInitializeMessage } from "./initialize.js";
9
+ import { withRequestContext, setRequestMcpClient } from "./request-context.js";
9
10
  import { SmartBearMcpServer } from "./server.js";
10
11
  import { isDraining, registerShutdownHandler } from "./shutdown.js";
11
12
  import { getEnvVarName } from "./transport-stdio.js";
@@ -211,19 +212,7 @@ async function createNewTransport(req, res, transports) {
211
212
  transports.set(newSessionId, { server, transport });
212
213
  }
213
214
  });
214
- transport.onmessage = (message) => {
215
- if ("method" in message && message.method === "initialize") {
216
- if (message.params?.protocolVersion === "2025-11-25") {
217
- const clientCapabilities = message.params?.capabilities;
218
- if (Object.hasOwn(clientCapabilities, "sampling")) {
219
- server.setSamplingSupported(true);
220
- }
221
- if (Object.hasOwn(clientCapabilities, "elicitation")) {
222
- server.setElicitationSupported(true);
223
- }
224
- }
225
- }
226
- };
215
+ transport.onmessage = (message) => handleInitializeMessage(server, message);
227
216
  transport.onclose = () => {
228
217
  if (transport.sessionId) {
229
218
  console.log(`[MCP] Session closed: ${transport.sessionId}`);
@@ -280,10 +269,13 @@ async function handleStreamableHttpRequest(req, res, transports) {
280
269
  );
281
270
  return;
282
271
  }
283
- await withRequestContext(
284
- req,
285
- async () => await transport.handleRequest(req, res, parsedBody)
286
- );
272
+ const sessionServer = sessionId ? transports.get(sessionId)?.server : void 0;
273
+ await withRequestContext(req, async () => {
274
+ if (sessionServer) {
275
+ setRequestMcpClient(sessionServer.getMcpClientIdentity());
276
+ }
277
+ return await transport.handleRequest(req, res, parsedBody);
278
+ });
287
279
  } catch (error) {
288
280
  console.error("Error handling StreamableHTTP request:", error);
289
281
  res.writeHead(500, { "Content-Type": "text/plain" });
@@ -296,6 +288,7 @@ async function handleLegacySseRequest(req, res, transports) {
296
288
  return;
297
289
  }
298
290
  const transport = new SSEServerTransport("/message", res);
291
+ transport.onmessage = (message) => handleInitializeMessage(server, message);
299
292
  transports.set(transport.sessionId, { server, transport });
300
293
  res.on("close", () => {
301
294
  transports.delete(transport.sessionId);
@@ -328,14 +321,10 @@ async function handleLegacyMessageRequest(req, res, url, transports) {
328
321
  req.on("end", async () => {
329
322
  try {
330
323
  const parsedBody = JSON.parse(body);
331
- await withRequestContext(
332
- req,
333
- async () => await session.transport.handlePostMessage(
334
- req,
335
- res,
336
- parsedBody
337
- )
338
- );
324
+ await withRequestContext(req, async () => {
325
+ setRequestMcpClient(session.server.getMcpClientIdentity());
326
+ return await session.transport.handlePostMessage(req, res, parsedBody);
327
+ });
339
328
  } catch (error) {
340
329
  console.error("Error handling POST message:", error);
341
330
  res.writeHead(500, { "Content-Type": "text/plain" });
@@ -2,6 +2,7 @@ import { enableCompileCache } from "node:module";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import { clientRegistry } from "./client-registry.js";
4
4
  import { USER_AGENT } from "./info.js";
5
+ import { handleInitializeMessage } from "./initialize.js";
5
6
  import { SmartBearMcpServer } from "./server.js";
6
7
  import { registerShutdownHandler } from "./shutdown.js";
7
8
  import { isOptionalType, getTypeDescription } from "./zod-utils.js";
@@ -57,19 +58,7 @@ ${message.join("\n")}` : "No clients support environment variable configuration.
57
58
  console.error("[MCP][shutdown] Error closing stdio transport:", err);
58
59
  }
59
60
  });
60
- transport.onmessage = (message) => {
61
- if ("method" in message && message.method === "initialize") {
62
- if (message.params?.protocolVersion === "2025-11-25") {
63
- const clientCapabilities = message.params?.capabilities;
64
- if (Object.hasOwn(clientCapabilities, "sampling")) {
65
- server.setSamplingSupported(true);
66
- }
67
- if (Object.hasOwn(clientCapabilities, "elicitation")) {
68
- server.setElicitationSupported(true);
69
- }
70
- }
71
- }
72
- };
61
+ transport.onmessage = (message) => handleInitializeMessage(server, message);
73
62
  await server.connect(transport);
74
63
  }
75
64
  function getEnvVarName(clientPrefix, key) {
@@ -1,4 +1,4 @@
1
- const version = "0.27.2";
1
+ const version = "0.29.0";
2
2
  const config = { "mcpServerName": "SmartBear MCP Server" };
3
3
  const packageJson = {
4
4
  version,
@@ -170,6 +170,10 @@ class PactflowClient {
170
170
  if (Array.isArray(contextToken)) {
171
171
  contextToken = contextToken[0];
172
172
  }
173
+ const clientInfo = this._server?.getClientInfo();
174
+ const sourceApplicationHeader = {
175
+ SOURCE_APPLICATION: clientInfo ? `${clientInfo.name}/${clientInfo.version}` : "unknown"
176
+ };
173
177
  if (contextToken) {
174
178
  let authHeader = contextToken;
175
179
  if (!contextToken.startsWith("Basic ") && !contextToken.startsWith("Bearer ")) {
@@ -178,7 +182,8 @@ class PactflowClient {
178
182
  return {
179
183
  Authorization: authHeader,
180
184
  "Content-Type": "application/json",
181
- "User-Agent": USER_AGENT
185
+ "User-Agent": USER_AGENT,
186
+ ...sourceApplicationHeader
182
187
  };
183
188
  }
184
189
  if (this.token) {
@@ -189,14 +194,16 @@ class PactflowClient {
189
194
  return {
190
195
  Authorization: authHeader,
191
196
  "Content-Type": "application/json",
192
- "User-Agent": USER_AGENT
197
+ "User-Agent": USER_AGENT,
198
+ ...sourceApplicationHeader
193
199
  };
194
200
  } else if (this.username && this.password) {
195
201
  const authString = `${this.username}:${this.password}`;
196
202
  return {
197
203
  Authorization: `Basic ${Buffer.from(authString).toString("base64")}`,
198
204
  "Content-Type": "application/json",
199
- "User-Agent": USER_AGENT
205
+ "User-Agent": USER_AGENT,
206
+ ...sourceApplicationHeader
200
207
  };
201
208
  }
202
209
  return void 0;
@@ -1877,7 +1884,7 @@ class PactflowClient {
1877
1884
  `${this.baseUrl}/admin/teams/${encodeURIComponent(teamId)}/users`,
1878
1885
  {
1879
1886
  method: "PUT",
1880
- body: { uuids },
1887
+ body: { users: uuids },
1881
1888
  errorContext: "Set Team Users"
1882
1889
  }
1883
1890
  );
@@ -1,4 +1,4 @@
1
- import { USER_AGENT } from "../../../common/info.js";
1
+ import { getUserAgent } from "../../../common/info.js";
2
2
  import { QMETRY_DEFAULTS } from "../../config/constants.js";
3
3
  import { handleQMetryFetchError, handleQMetryApiError } from "./error-handler.js";
4
4
  async function qmetryRequest({
@@ -16,7 +16,7 @@ async function qmetryRequest({
16
16
  const headers = {
17
17
  apikey: token,
18
18
  project: project || QMETRY_DEFAULTS.PROJECT_KEY,
19
- "User-Agent": USER_AGENT,
19
+ "User-Agent": getUserAgent(),
20
20
  "qmetry-source": "smartbear-mcp"
21
21
  };
22
22
  if (scopeId !== void 0) {
@@ -1,4 +1,4 @@
1
- import { USER_AGENT } from "../../common/info.js";
1
+ import { getUserAgent } from "../../common/info.js";
2
2
  import { QMETRY_DEFAULTS } from "../config/constants.js";
3
3
  import { QMETRY_PATHS } from "../config/rest-endpoints.js";
4
4
  import { DEFAULT_IMPORT_AUTOMATION_PAYLOAD } from "../types/automation.js";
@@ -91,7 +91,7 @@ async function importAutomationResults(token, baseUrl, project, payload) {
91
91
  const headers = {
92
92
  apikey: token,
93
93
  project: finalPayload.projectID || project || QMETRY_DEFAULTS.PROJECT_KEY,
94
- "User-Agent": USER_AGENT,
94
+ "User-Agent": getUserAgent(),
95
95
  "qmetry-source": "smartbear-mcp"
96
96
  // Note: Content-Type will be set automatically by fetch for FormData
97
97
  };
@@ -1,4 +1,4 @@
1
- import { USER_AGENT } from "../../common/info.js";
1
+ import { getUserAgent } from "../../common/info.js";
2
2
  import { CONTENT_TYPES, HTTP_HEADERS } from "../config/constants.js";
3
3
  class AuthService {
4
4
  apiKey;
@@ -13,7 +13,7 @@ class AuthService {
13
13
  return {
14
14
  [HTTP_HEADERS.API_KEY]: this.apiKey,
15
15
  [HTTP_HEADERS.CONTENT_TYPE]: CONTENT_TYPES.JSON,
16
- [HTTP_HEADERS.USER_AGENT]: USER_AGENT,
16
+ [HTTP_HEADERS.USER_AGENT]: getUserAgent(),
17
17
  [HTTP_HEADERS.ACCEPT]: CONTENT_TYPES.JSON
18
18
  };
19
19
  }
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { USER_AGENT } from "../common/info.js";
2
+ import { getUserAgent } from "../common/info.js";
3
3
  import { getRequestHeader } from "../common/request-context.js";
4
4
  import { ToolError } from "../common/tools.js";
5
5
  import { API_KEY_HEADER, REFLECT_API_TOKEN_HEADER, AUTHORIZATION_HEADER } from "./config/constants.js";
@@ -73,7 +73,7 @@ class ReflectClient {
73
73
  return {
74
74
  ...this.getAuthHeader(),
75
75
  "Content-Type": "application/json",
76
- "User-Agent": USER_AGENT
76
+ "User-Agent": getUserAgent()
77
77
  };
78
78
  }
79
79
  getSessionState(sessionId) {
@@ -1,3 +1,4 @@
1
+ import { appendClientIdentity } from "../../common/info.js";
1
2
  import { ToolError } from "../../common/tools.js";
2
3
  import { buildSubdomainCandidate, buildSuffixedSubdomain, buildPortalName, isConflictError, isOrganizationPortalConflict } from "./portal-utils.js";
3
4
  import { findTableOfContentsItem, buildPortalLiveUrl, normalizeSlug } from "./utils.js";
@@ -22,7 +23,7 @@ class SwaggerAPI {
22
23
  this.userAgent = userAgent;
23
24
  }
24
25
  get headers() {
25
- return this.config.getHeaders(this.userAgent);
26
+ return this.config.getHeaders(appendClientIdentity(this.userAgent));
26
27
  }
27
28
  /**
28
29
  * Core response parsing logic shared between different response handlers.
@@ -677,7 +678,8 @@ class SwaggerAPI {
677
678
  contentType = "markdown",
678
679
  source = "internal",
679
680
  order = 0,
680
- parentId = null
681
+ parentId = null,
682
+ pageSlug
681
683
  } = args;
682
684
  if (contentType === "html" && source === "internal" && pageContent !== void 0) {
683
685
  throw new ToolError(
@@ -694,12 +696,12 @@ class SwaggerAPI {
694
696
  );
695
697
  }
696
698
  const section = sections.items[0];
697
- const pageSlug = normalizeSlug(pageTitle);
699
+ const resolvedPageSlug = pageSlug || normalizeSlug(pageTitle);
698
700
  const normalizedTitle = pageTitle.slice(0, 255);
699
701
  const tocItem = await this.createTableOfContents(section.id, {
700
702
  type: "new",
701
703
  title: normalizedTitle,
702
- slug: pageSlug,
704
+ slug: resolvedPageSlug,
703
705
  order,
704
706
  parentId,
705
707
  content: {
@@ -724,7 +726,7 @@ class SwaggerAPI {
724
726
  sectionSlug: section.slug,
725
727
  pageDetails: {
726
728
  tableOfContentsId: tocItem.id,
727
- slug: pageSlug,
729
+ slug: resolvedPageSlug,
728
730
  title: normalizedTitle,
729
731
  content: {
730
732
  type: contentType,