@smartbear/mcp 0.28.0 → 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 +1 -1
- package/dist/bearq/client.js +5 -3
- package/dist/bearq/tool/environments/list-environments.js +28 -0
- package/dist/bearq/tool/tasks/run-regression-tests.js +10 -3
- package/dist/bearq/tool/tasks/run-test-cases.js +12 -3
- package/dist/bearq/tool/tasks/run-tests-in-functional-areas.js +12 -3
- package/dist/bugsnag/client.js +2 -2
- package/dist/collaborator/client.js +5 -2
- package/dist/common/client-identity.js +20 -0
- package/dist/common/info.js +21 -1
- package/dist/common/initialize.js +7 -0
- package/dist/common/request-context.js +7 -0
- package/dist/common/server.js +32 -0
- package/dist/common/transport-http.js +13 -13
- package/dist/package.json.js +1 -1
- package/dist/pactflow/client.js +1 -1
- package/dist/qmetry/client/api/client-api.js +2 -2
- package/dist/qmetry/client/automation.js +2 -2
- package/dist/qtm4j/http/auth-service.js +2 -2
- package/dist/reflect/client.js +2 -2
- package/dist/swagger/client/api.js +2 -1
- package/dist/swagger/client/functional-testing-api.js +171 -14
- package/dist/swagger/client/functional-testing-tools.js +41 -2
- package/dist/swagger/client/functional-testing-types.js +21 -0
- package/dist/swagger/client.js +16 -0
- package/dist/zephyr/common/auth-service.js +2 -2
- package/dist/zephyr/common/rest-api-schemas.js +1 -1
- package/package.json +3 -1
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
|
package/dist/bearq/client.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
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":
|
|
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 (
|
|
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(
|
|
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(
|
|
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(
|
|
27
|
+
body: JSON.stringify(body)
|
|
19
28
|
});
|
|
20
29
|
if (!res.ok)
|
|
21
30
|
throw new ToolError(
|
package/dist/bugsnag/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
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":
|
|
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 {
|
|
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: {
|
|
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
|
+
};
|
package/dist/common/info.js
CHANGED
|
@@ -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
|
};
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
import { toClientIdentity, setProcessClientIdentity } from "./client-identity.js";
|
|
1
2
|
function handleInitializeMessage(server, message) {
|
|
2
3
|
if (typeof message !== "object" || message === null || !("method" in message) || message.method !== "initialize") {
|
|
3
4
|
return;
|
|
4
5
|
}
|
|
5
6
|
const params = message.params;
|
|
7
|
+
const identity = toClientIdentity(
|
|
8
|
+
params?.clientInfo,
|
|
9
|
+
params?.protocolVersion
|
|
10
|
+
);
|
|
11
|
+
server.setMcpClientIdentity(identity);
|
|
12
|
+
setProcessClientIdentity(identity);
|
|
6
13
|
const clientInfo = params?.clientInfo;
|
|
7
14
|
if (clientInfo) {
|
|
8
15
|
server.setClientInfo(clientInfo);
|
|
@@ -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
|
};
|
package/dist/common/server.js
CHANGED
|
@@ -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";
|
|
@@ -13,6 +14,7 @@ class SmartBearMcpServer extends McpServer {
|
|
|
13
14
|
clientInfo;
|
|
14
15
|
clients = [];
|
|
15
16
|
enabledToolsets;
|
|
17
|
+
mcpClientIdentity;
|
|
16
18
|
constructor(enabledToolsets) {
|
|
17
19
|
super(
|
|
18
20
|
{
|
|
@@ -58,6 +60,33 @@ class SmartBearMcpServer extends McpServer {
|
|
|
58
60
|
getClients() {
|
|
59
61
|
return this.clients;
|
|
60
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
|
+
}
|
|
61
90
|
async cleanupSession(mcpSessionId) {
|
|
62
91
|
for (const client of this.clients) {
|
|
63
92
|
await client.cleanupSession?.(mcpSessionId);
|
|
@@ -113,6 +142,7 @@ class SmartBearMcpServer extends McpServer {
|
|
|
113
142
|
} else {
|
|
114
143
|
Bugsnag.notify(e, (event) => {
|
|
115
144
|
event.addMetadata("app", { tool: toolName });
|
|
145
|
+
this.addClientMetadata(event);
|
|
116
146
|
event.unhandled = true;
|
|
117
147
|
});
|
|
118
148
|
}
|
|
@@ -163,6 +193,7 @@ ${result.instructions}`
|
|
|
163
193
|
resource: resourceName,
|
|
164
194
|
url: url2
|
|
165
195
|
});
|
|
196
|
+
this.addClientMetadata(event);
|
|
166
197
|
event.unhandled = true;
|
|
167
198
|
});
|
|
168
199
|
throw e;
|
|
@@ -188,6 +219,7 @@ ${result.instructions}`
|
|
|
188
219
|
event.addMetadata("app", {
|
|
189
220
|
prompt: this.getCapabilityName(client, params.title)
|
|
190
221
|
});
|
|
222
|
+
this.addClientMetadata(event);
|
|
191
223
|
event.unhandled = true;
|
|
192
224
|
});
|
|
193
225
|
throw e;
|
|
@@ -6,7 +6,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
6
6
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
7
7
|
import { clientRegistry } from "./client-registry.js";
|
|
8
8
|
import { handleInitializeMessage } from "./initialize.js";
|
|
9
|
-
import { withRequestContext } from "./request-context.js";
|
|
9
|
+
import { withRequestContext, setRequestMcpClient } from "./request-context.js";
|
|
10
10
|
import { SmartBearMcpServer } from "./server.js";
|
|
11
11
|
import { isDraining, registerShutdownHandler } from "./shutdown.js";
|
|
12
12
|
import { getEnvVarName } from "./transport-stdio.js";
|
|
@@ -269,10 +269,13 @@ async function handleStreamableHttpRequest(req, res, transports) {
|
|
|
269
269
|
);
|
|
270
270
|
return;
|
|
271
271
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
+
});
|
|
276
279
|
} catch (error) {
|
|
277
280
|
console.error("Error handling StreamableHTTP request:", error);
|
|
278
281
|
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
@@ -285,6 +288,7 @@ async function handleLegacySseRequest(req, res, transports) {
|
|
|
285
288
|
return;
|
|
286
289
|
}
|
|
287
290
|
const transport = new SSEServerTransport("/message", res);
|
|
291
|
+
transport.onmessage = (message) => handleInitializeMessage(server, message);
|
|
288
292
|
transports.set(transport.sessionId, { server, transport });
|
|
289
293
|
res.on("close", () => {
|
|
290
294
|
transports.delete(transport.sessionId);
|
|
@@ -317,14 +321,10 @@ async function handleLegacyMessageRequest(req, res, url, transports) {
|
|
|
317
321
|
req.on("end", async () => {
|
|
318
322
|
try {
|
|
319
323
|
const parsedBody = JSON.parse(body);
|
|
320
|
-
await withRequestContext(
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
res,
|
|
325
|
-
parsedBody
|
|
326
|
-
)
|
|
327
|
-
);
|
|
324
|
+
await withRequestContext(req, async () => {
|
|
325
|
+
setRequestMcpClient(session.server.getMcpClientIdentity());
|
|
326
|
+
return await session.transport.handlePostMessage(req, res, parsedBody);
|
|
327
|
+
});
|
|
328
328
|
} catch (error) {
|
|
329
329
|
console.error("Error handling POST message:", error);
|
|
330
330
|
res.writeHead(500, { "Content-Type": "text/plain" });
|
package/dist/package.json.js
CHANGED
package/dist/pactflow/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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":
|
|
19
|
+
"User-Agent": getUserAgent(),
|
|
20
20
|
"qmetry-source": "smartbear-mcp"
|
|
21
21
|
};
|
|
22
22
|
if (scopeId !== void 0) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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":
|
|
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 {
|
|
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]:
|
|
16
|
+
[HTTP_HEADERS.USER_AGENT]: getUserAgent(),
|
|
17
17
|
[HTTP_HEADERS.ACCEPT]: CONTENT_TYPES.JSON
|
|
18
18
|
};
|
|
19
19
|
}
|
package/dist/reflect/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
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":
|
|
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.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { appendClientIdentity } from "../../common/info.js";
|
|
1
2
|
import { ToolError } from "../../common/tools.js";
|
|
2
3
|
const API_HOSTNAME = "api.reflect.run";
|
|
3
4
|
const FUNCTIONAL_TESTING_API_KEY_HEADER = "X-API-KEY";
|
|
@@ -16,11 +17,27 @@ class FunctionalTestingAPI {
|
|
|
16
17
|
return {
|
|
17
18
|
[FUNCTIONAL_TESTING_API_KEY_HEADER]: token,
|
|
18
19
|
"Content-Type": "application/json",
|
|
19
|
-
"User-Agent": this.userAgent
|
|
20
|
+
"User-Agent": appendClientIdentity(this.userAgent)
|
|
20
21
|
};
|
|
21
22
|
}
|
|
23
|
+
async ftFetch(input, init) {
|
|
24
|
+
let response;
|
|
25
|
+
try {
|
|
26
|
+
response = await fetch(input, init);
|
|
27
|
+
} catch {
|
|
28
|
+
throw new ToolError(
|
|
29
|
+
"Swagger Functional Testing service is currently unreachable. Retry after a moment."
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
if (response.status === 401 || response.status === 403) {
|
|
33
|
+
throw new ToolError(
|
|
34
|
+
"Authentication failed. Verify your API token is valid and has not expired."
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return response;
|
|
38
|
+
}
|
|
22
39
|
async listTests() {
|
|
23
|
-
const response = await
|
|
40
|
+
const response = await this.ftFetch(`${this.baseUrl}/tests`, {
|
|
24
41
|
method: "GET",
|
|
25
42
|
headers: this.getFtHeaders()
|
|
26
43
|
});
|
|
@@ -33,8 +50,8 @@ class FunctionalTestingAPI {
|
|
|
33
50
|
}
|
|
34
51
|
async runTest(args) {
|
|
35
52
|
if (!args.testId) throw new ToolError("testId argument is required");
|
|
36
|
-
const response = await
|
|
37
|
-
`${this.baseUrl}/tests/${args.testId}/executions`,
|
|
53
|
+
const response = await this.ftFetch(
|
|
54
|
+
`${this.baseUrl}/tests/${encodeURIComponent(args.testId)}/executions`,
|
|
38
55
|
{
|
|
39
56
|
method: "POST",
|
|
40
57
|
headers: this.getFtHeaders()
|
|
@@ -51,8 +68,8 @@ class FunctionalTestingAPI {
|
|
|
51
68
|
if (!args.executionId) {
|
|
52
69
|
throw new ToolError("executionId argument is required");
|
|
53
70
|
}
|
|
54
|
-
const response = await
|
|
55
|
-
`${this.baseUrl}/executions/${args.executionId}`,
|
|
71
|
+
const response = await this.ftFetch(
|
|
72
|
+
`${this.baseUrl}/executions/${encodeURIComponent(args.executionId)}`,
|
|
56
73
|
{
|
|
57
74
|
method: "GET",
|
|
58
75
|
headers: this.getFtHeaders()
|
|
@@ -63,16 +80,87 @@ class FunctionalTestingAPI {
|
|
|
63
80
|
`Failed to get test status: ${response.status} ${response.statusText}`
|
|
64
81
|
);
|
|
65
82
|
}
|
|
66
|
-
|
|
83
|
+
const data = await response.json();
|
|
84
|
+
if (Array.isArray(data.tests)) {
|
|
85
|
+
for (const test of data.tests) {
|
|
86
|
+
const run = test.run;
|
|
87
|
+
if (run) {
|
|
88
|
+
delete run.videoUrl;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return data;
|
|
93
|
+
}
|
|
94
|
+
async listSuiteExecutions(args) {
|
|
95
|
+
if (!args.suiteId) throw new ToolError("suiteId argument is required");
|
|
96
|
+
const response = await this.ftFetch(
|
|
97
|
+
`${this.baseUrl}/suites/${encodeURIComponent(args.suiteId)}/executions`,
|
|
98
|
+
{
|
|
99
|
+
method: "GET",
|
|
100
|
+
headers: this.getFtHeaders()
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
if (!response.ok) {
|
|
104
|
+
throw new ToolError(suiteExecutionsErrorMessage(response));
|
|
105
|
+
}
|
|
106
|
+
const data = await response.json();
|
|
107
|
+
return {
|
|
108
|
+
...data,
|
|
109
|
+
executions: {
|
|
110
|
+
data: data.executions.data.map(
|
|
111
|
+
({ executionId, status, isFinished }) => ({
|
|
112
|
+
executionId,
|
|
113
|
+
status,
|
|
114
|
+
isFinished
|
|
115
|
+
})
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
};
|
|
67
119
|
}
|
|
68
120
|
async listSuites() {
|
|
69
|
-
const
|
|
121
|
+
const response = await this.ftFetch(`${this.baseUrl}/suites`, {
|
|
122
|
+
method: "GET",
|
|
123
|
+
headers: this.getFtHeaders()
|
|
124
|
+
});
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
throw new ToolError(
|
|
127
|
+
`Failed to list Functional Testing suites: ${response.status} ${response.statusText}`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
return response.json();
|
|
131
|
+
}
|
|
132
|
+
async cancelSuiteExecution(args) {
|
|
133
|
+
if (!args.suiteId) throw new ToolError("suiteId argument is required");
|
|
134
|
+
if (!args.executionId) {
|
|
135
|
+
throw new ToolError("executionId argument is required");
|
|
136
|
+
}
|
|
137
|
+
const response = await this.ftFetch(
|
|
138
|
+
`${this.baseUrl}/suites/${encodeURIComponent(args.suiteId)}/executions/${encodeURIComponent(args.executionId)}/cancel`,
|
|
139
|
+
{
|
|
140
|
+
method: "PATCH",
|
|
141
|
+
headers: this.getFtHeaders()
|
|
142
|
+
}
|
|
143
|
+
);
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
throw new ToolError(cancelSuiteExecutionErrorMessage(response));
|
|
146
|
+
}
|
|
147
|
+
return response.json();
|
|
148
|
+
}
|
|
149
|
+
async runSuite(args) {
|
|
150
|
+
if (!args.suiteId) throw new ToolError("suiteId argument is required");
|
|
151
|
+
const body = args.tunnelAgentName ? JSON.stringify({
|
|
152
|
+
overrides: { agent: { name: args.tunnelAgentName } }
|
|
153
|
+
}) : void 0;
|
|
70
154
|
let response;
|
|
71
155
|
try {
|
|
72
|
-
response = await fetch(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
156
|
+
response = await fetch(
|
|
157
|
+
`${this.baseUrl}/suites/${args.suiteId}/executions`,
|
|
158
|
+
{
|
|
159
|
+
method: "POST",
|
|
160
|
+
headers: this.getFtHeaders(),
|
|
161
|
+
body
|
|
162
|
+
}
|
|
163
|
+
);
|
|
76
164
|
} catch {
|
|
77
165
|
throw new ToolError(
|
|
78
166
|
"Swagger Functional Testing service is currently unreachable. Retry after a moment."
|
|
@@ -85,10 +173,79 @@ class FunctionalTestingAPI {
|
|
|
85
173
|
}
|
|
86
174
|
if (!response.ok) {
|
|
87
175
|
throw new ToolError(
|
|
88
|
-
`Failed to
|
|
176
|
+
`Failed to run suite: ${response.status} ${response.statusText}`
|
|
89
177
|
);
|
|
90
178
|
}
|
|
91
|
-
return
|
|
179
|
+
return this.withoutField("url", response);
|
|
180
|
+
}
|
|
181
|
+
async getSuiteExecution(args) {
|
|
182
|
+
if (!args.suiteId) throw new ToolError("suiteId argument is required");
|
|
183
|
+
if (!args.executionId) {
|
|
184
|
+
throw new ToolError("executionId argument is required");
|
|
185
|
+
}
|
|
186
|
+
let response;
|
|
187
|
+
try {
|
|
188
|
+
response = await fetch(
|
|
189
|
+
`${this.baseUrl}/suites/${args.suiteId}/executions/${args.executionId}`,
|
|
190
|
+
{
|
|
191
|
+
method: "GET",
|
|
192
|
+
headers: this.getFtHeaders()
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
} catch {
|
|
196
|
+
throw new ToolError(
|
|
197
|
+
"Swagger Functional Testing service is currently unreachable. Retry after a moment."
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
if (response.status === 401 || response.status === 403) {
|
|
201
|
+
throw new ToolError(
|
|
202
|
+
"Authentication failed. Verify your API token is valid and has not expired."
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
if (!response.ok) {
|
|
206
|
+
throw new ToolError(
|
|
207
|
+
`Failed to get suite execution status: ${response.status} ${response.statusText}`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
const data = await this.withoutField("url", response);
|
|
211
|
+
const testsData = data.tests?.data;
|
|
212
|
+
if (Array.isArray(testsData)) {
|
|
213
|
+
for (const test of testsData) {
|
|
214
|
+
if (Array.isArray(test.runs)) {
|
|
215
|
+
for (const run of test.runs) {
|
|
216
|
+
delete run.videoUrl;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return data;
|
|
222
|
+
}
|
|
223
|
+
async withoutField(field, response) {
|
|
224
|
+
const data = await response.json();
|
|
225
|
+
delete data[field];
|
|
226
|
+
return data;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function suiteExecutionsErrorMessage(response) {
|
|
230
|
+
switch (response.status) {
|
|
231
|
+
// Defensive: the Reflect API currently returns 200 with an empty
|
|
232
|
+
// `executions.data` list for an unknown suiteId rather than a 404, so this
|
|
233
|
+
// branch is not expected to fire today. Kept in case the API starts
|
|
234
|
+
// returning 404 for missing suites.
|
|
235
|
+
case 404:
|
|
236
|
+
return "Test suite not found. Verify the suiteId is correct and belongs to your workspace.";
|
|
237
|
+
default:
|
|
238
|
+
return `Failed to list suite executions: ${response.status} ${response.statusText}`;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function cancelSuiteExecutionErrorMessage(response) {
|
|
242
|
+
switch (response.status) {
|
|
243
|
+
case 404:
|
|
244
|
+
return "Suite execution not found. Verify the suiteId and executionId are correct and belong to your workspace.";
|
|
245
|
+
case 409:
|
|
246
|
+
return "Suite execution cannot be cancelled because it has already finished.";
|
|
247
|
+
default:
|
|
248
|
+
return `Failed to cancel suite execution: ${response.status} ${response.statusText}`;
|
|
92
249
|
}
|
|
93
250
|
}
|
|
94
251
|
export {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { RunFunctionalTestingTestParamsSchema, GetFunctionalTestingExecutionTestSchema } from "./functional-testing-types.js";
|
|
1
|
+
import { RunFunctionalTestingTestParamsSchema, GetFunctionalTestingExecutionTestSchema, ListFunctionalTestingSuiteExecutionsSchema, CancelFunctionalTestingSuiteExecutionSchema, RunFunctionalTestingSuiteParamsSchema, GetFunctionalTestingSuiteExecutionSchema } from "./functional-testing-types.js";
|
|
2
2
|
const FUNCTIONAL_TESTING_TOOLS = [
|
|
3
3
|
{
|
|
4
4
|
title: "List Tests",
|
|
5
5
|
toolset: "Functional Testing",
|
|
6
6
|
summary: "Lists all API tests available in your Swagger Functional Testing account. Use this tool when you need to discover available tests before running them or checking their status. Do not use this tool to retrieve test execution results or history.",
|
|
7
|
-
handler: "listFunctionalTestingTests"
|
|
7
|
+
handler: "listFunctionalTestingTests",
|
|
8
|
+
idempotent: true,
|
|
9
|
+
readOnly: true
|
|
8
10
|
},
|
|
9
11
|
{
|
|
10
12
|
title: "Run Test",
|
|
@@ -21,6 +23,25 @@ const FUNCTIONAL_TESTING_TOOLS = [
|
|
|
21
23
|
summary: "Get the status of a Swagger Functional Testing test execution. It returns information about the execution such as its status (running, passed or failed), run time, as well as the break down of the status of each test step.",
|
|
22
24
|
inputSchema: GetFunctionalTestingExecutionTestSchema,
|
|
23
25
|
handler: "getFunctionalTestingExecution",
|
|
26
|
+
idempotent: true,
|
|
27
|
+
readOnly: true
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
title: "List Suite Executions",
|
|
31
|
+
toolset: "Functional Testing",
|
|
32
|
+
summary: "Lists all executions for a given test suite in your Swagger Functional Testing workspace. Use this tool when you need to review execution history and timings for a specific suite. Do not use this tool to retrieve the status of a single execution or individual test results.",
|
|
33
|
+
inputSchema: ListFunctionalTestingSuiteExecutionsSchema,
|
|
34
|
+
handler: "listFunctionalTestingSuiteExecutions",
|
|
35
|
+
readOnly: true,
|
|
36
|
+
idempotent: true
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
title: "Cancel Suite Execution",
|
|
40
|
+
toolset: "Functional Testing",
|
|
41
|
+
summary: "Cancels an ongoing test suite execution in your Swagger Functional Testing workspace. Use this tool when you need to stop a long-running or accidentally triggered suite run. Do not use this tool to cancel individual test runs.",
|
|
42
|
+
inputSchema: CancelFunctionalTestingSuiteExecutionSchema,
|
|
43
|
+
handler: "cancelFunctionalTestingSuiteExecution",
|
|
44
|
+
readOnly: false,
|
|
24
45
|
idempotent: false
|
|
25
46
|
},
|
|
26
47
|
{
|
|
@@ -30,6 +51,24 @@ const FUNCTIONAL_TESTING_TOOLS = [
|
|
|
30
51
|
handler: "listFunctionalTestingSuites",
|
|
31
52
|
idempotent: true,
|
|
32
53
|
readOnly: true
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
title: "Run Suite",
|
|
57
|
+
toolset: "Functional Testing",
|
|
58
|
+
summary: "Runs a specific test suite in your Swagger Functional Testing workspace. The execution is asynchronous — it returns an executionId, not results directly. Use `swagger_get_suite_status` with your suiteId and executionId to track progress and retrieve the final per-test results. Optionally accepts a `tunnelAgentName` argument to override the suite's saved tunnel for this run. Do not use this tool to run a single test — use `swagger_run_test` instead.",
|
|
59
|
+
inputSchema: RunFunctionalTestingSuiteParamsSchema,
|
|
60
|
+
handler: "runFunctionalTestingSuite",
|
|
61
|
+
idempotent: false,
|
|
62
|
+
readOnly: false
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
title: "Get Suite Status",
|
|
66
|
+
toolset: "Functional Testing",
|
|
67
|
+
summary: "Get the status of a Swagger Functional Testing suite execution. Returns the overall status (pending, canceled, passed or failed), whether the run is finished, and a per-test breakdown with pass/fail. Use this to poll for the outcome of a suite run triggered by `swagger_run_suite`. Requires both `suiteId` and the `executionId` arguments returned by `swagger_run_suite`.",
|
|
68
|
+
inputSchema: GetFunctionalTestingSuiteExecutionSchema,
|
|
69
|
+
handler: "getFunctionalTestingSuiteExecution",
|
|
70
|
+
idempotent: true,
|
|
71
|
+
readOnly: true
|
|
33
72
|
}
|
|
34
73
|
];
|
|
35
74
|
export {
|
|
@@ -5,7 +5,28 @@ const RunFunctionalTestingTestParamsSchema = z.object({
|
|
|
5
5
|
const GetFunctionalTestingExecutionTestSchema = z.object({
|
|
6
6
|
executionId: z.string().describe("ID of the Functional Testing execution").trim().min(1)
|
|
7
7
|
});
|
|
8
|
+
const ListFunctionalTestingSuiteExecutionsSchema = z.object({
|
|
9
|
+
suiteId: z.string().describe("ID of the Functional Testing suite to list executions for").trim().min(1)
|
|
10
|
+
});
|
|
11
|
+
const CancelFunctionalTestingSuiteExecutionSchema = z.object({
|
|
12
|
+
suiteId: z.string().describe("ID of the Functional Testing suite the execution belongs to").trim().min(1),
|
|
13
|
+
executionId: z.string().describe("ID of the Functional Testing suite execution to cancel").trim().min(1)
|
|
14
|
+
});
|
|
15
|
+
const RunFunctionalTestingSuiteParamsSchema = z.object({
|
|
16
|
+
suiteId: z.string().describe("ID of the Functional Testing suite to run").trim().min(1),
|
|
17
|
+
tunnelAgentName: z.string().describe(
|
|
18
|
+
"Optional tunnel agent name to override the suite's saved tunnel for this run. When omitted, the suite's saved tunnel overrides are used, falling back to each test's saved tunnel."
|
|
19
|
+
).trim().min(1).optional()
|
|
20
|
+
});
|
|
21
|
+
const GetFunctionalTestingSuiteExecutionSchema = z.object({
|
|
22
|
+
suiteId: z.string().describe("ID of the Functional Testing suite").trim().min(1),
|
|
23
|
+
executionId: z.string().describe("ID of the Functional Testing suite execution").trim().min(1)
|
|
24
|
+
});
|
|
8
25
|
export {
|
|
26
|
+
CancelFunctionalTestingSuiteExecutionSchema,
|
|
9
27
|
GetFunctionalTestingExecutionTestSchema,
|
|
28
|
+
GetFunctionalTestingSuiteExecutionSchema,
|
|
29
|
+
ListFunctionalTestingSuiteExecutionsSchema,
|
|
30
|
+
RunFunctionalTestingSuiteParamsSchema,
|
|
10
31
|
RunFunctionalTestingTestParamsSchema
|
|
11
32
|
};
|
package/dist/swagger/client.js
CHANGED
|
@@ -181,6 +181,22 @@ class SwaggerClient {
|
|
|
181
181
|
async getFunctionalTestingExecution(args) {
|
|
182
182
|
return this.withFunctionalTesting((ftApi) => ftApi.getTestExecution(args));
|
|
183
183
|
}
|
|
184
|
+
async listFunctionalTestingSuiteExecutions(args) {
|
|
185
|
+
return this.withFunctionalTesting(
|
|
186
|
+
(ftApi) => ftApi.listSuiteExecutions(args)
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
async cancelFunctionalTestingSuiteExecution(args) {
|
|
190
|
+
return this.withFunctionalTesting(
|
|
191
|
+
(ftApi) => ftApi.cancelSuiteExecution(args)
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
async runFunctionalTestingSuite(args) {
|
|
195
|
+
return this.withFunctionalTesting((ftApi) => ftApi.runSuite(args));
|
|
196
|
+
}
|
|
197
|
+
async getFunctionalTestingSuiteExecution(args) {
|
|
198
|
+
return this.withFunctionalTesting((ftApi) => ftApi.getSuiteExecution(args));
|
|
199
|
+
}
|
|
184
200
|
/**
|
|
185
201
|
* Perform an operation with the Functional Testing API.
|
|
186
202
|
* Throws a ToolError if Functional Testing is not configured
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getUserAgent } from "../../common/info.js";
|
|
2
2
|
class AuthService {
|
|
3
3
|
bearerToken;
|
|
4
4
|
constructor(accessToken) {
|
|
@@ -8,7 +8,7 @@ class AuthService {
|
|
|
8
8
|
return {
|
|
9
9
|
Authorization: `Bearer ${this.bearerToken}`,
|
|
10
10
|
"Content-Type": "application/json",
|
|
11
|
-
"User-Agent":
|
|
11
|
+
"User-Agent": getUserAgent(),
|
|
12
12
|
"zscale-source": "smartbear-mcp"
|
|
13
13
|
};
|
|
14
14
|
}
|
|
@@ -144,7 +144,7 @@ const createTestCaseBodyProjectKeyRegExp = /([A-Z][A-Z_0-9]+)/;
|
|
|
144
144
|
const createTestCaseBodyNameMax = 255;
|
|
145
145
|
const createTestCaseBodyNameRegExp = /^(?!\\s*$).+/;
|
|
146
146
|
const createTestCaseBodyEstimatedTimeMin = 0;
|
|
147
|
-
const createTestCaseBodyComponentIdMin =
|
|
147
|
+
const createTestCaseBodyComponentIdMin = 1;
|
|
148
148
|
const createTestCaseBodyPriorityNameMax = 255;
|
|
149
149
|
const createTestCaseBodyStatusNameMax = 255;
|
|
150
150
|
const createTestCaseBodyOwnerIdRegExp = /^[-:a-zA-Z0-9]{1,128}$/;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartbear/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "MCP server for interacting SmartBear Products",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"smartbear",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"test:coverage": "vitest --coverage",
|
|
46
46
|
"test:coverage:ci": "vitest --coverage --reporter=verbose",
|
|
47
47
|
"test:run": "vitest run",
|
|
48
|
+
"test:pact": "vitest run --config vitest.pact.config.ts",
|
|
48
49
|
"coverage:check": "vitest --coverage --reporter=verbose",
|
|
49
50
|
"bump": "node scripts/bump.js"
|
|
50
51
|
},
|
|
@@ -60,6 +61,7 @@
|
|
|
60
61
|
},
|
|
61
62
|
"devDependencies": {
|
|
62
63
|
"@biomejs/biome": "^2.4.8",
|
|
64
|
+
"@pact-foundation/pact": "^17.0.1",
|
|
63
65
|
"@types/js-yaml": "^4.0.9",
|
|
64
66
|
"@types/node": "^22.19.15",
|
|
65
67
|
"@types/ws": "^8.18.1",
|