@smartbear/mcp 0.25.0 → 0.26.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.
@@ -1,7 +1,8 @@
1
1
  import { z } from "zod";
2
- import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info.js";
2
+ import { USER_AGENT } from "../common/info.js";
3
+ import { getRequestHeader } from "../common/request-context.js";
3
4
  import { ToolError } from "../common/tools.js";
4
- import { API_KEY_HEADER, AUTHORIZATION_HEADER } from "./config/constants.js";
5
+ import { API_KEY_HEADER, REFLECT_API_TOKEN_HEADER, AUTHORIZATION_HEADER } from "./config/constants.js";
5
6
  import { SapTest } from "./prompt/sap-test.js";
6
7
  import { AddPromptStep } from "./tool/recording/add-prompt-step.js";
7
8
  import { AddSegment } from "./tool/recording/add-segment.js";
@@ -13,16 +14,16 @@ import { ExecuteSuite } from "./tool/suites/execute-suite.js";
13
14
  import { GetSuiteExecutionStatus } from "./tool/suites/get-suite-execution-status.js";
14
15
  import { ListSuiteExecutions } from "./tool/suites/list-suite-executions.js";
15
16
  import { ListSuites } from "./tool/suites/list-suites.js";
17
+ import { GetTestDetail } from "./tool/tests/get-test-detail.js";
16
18
  import { GetTestStatus } from "./tool/tests/get-test-status.js";
17
19
  import { ListSegments } from "./tool/tests/list-segments.js";
18
20
  import { ListTests } from "./tool/tests/list-tests.js";
19
21
  import { RunTest } from "./tool/tests/run-test.js";
20
- const ConfigurationSchema = z.object({});
21
- const AuthenticationSchema = z.object({
22
- api_token: z.string().describe("Reflect API authentication token").optional()
22
+ const ConfigurationSchema = z.object({
23
+ api_token: z.string().describe("Reflect API authentication token")
23
24
  });
24
25
  class ReflectClient {
25
- _server;
26
+ _apiToken;
26
27
  activeConnections = /* @__PURE__ */ new Map();
27
28
  sessionStates = /* @__PURE__ */ new Map();
28
29
  mcpSessionConnections = /* @__PURE__ */ new Map();
@@ -30,24 +31,33 @@ class ReflectClient {
30
31
  capabilityPrefix = "reflect";
31
32
  configPrefix = "Reflect";
32
33
  config = ConfigurationSchema;
33
- authenticationFields = AuthenticationSchema;
34
- async configure(server, _config) {
35
- this._server = server;
34
+ async configure(_server, config, _cache) {
35
+ this._apiToken = config.api_token;
36
36
  }
37
37
  getAuthToken() {
38
- return this._server?.getEnv("api_token", this) || this._server?.getEnv(API_KEY_HEADER) || this._server?.getEnv(AUTHORIZATION_HEADER) || null;
38
+ const contextHeader = getRequestHeader(REFLECT_API_TOKEN_HEADER) || getRequestHeader(API_KEY_HEADER) || getRequestHeader(AUTHORIZATION_HEADER);
39
+ if (contextHeader) {
40
+ let token = Array.isArray(contextHeader) ? contextHeader[0] : contextHeader;
41
+ if (token.startsWith("Bearer ")) {
42
+ token = token.substring(7);
43
+ }
44
+ return token;
45
+ }
46
+ return this._apiToken || null;
39
47
  }
40
48
  isConfigured() {
41
- return !!this._server;
42
- }
43
- hasAuth() {
44
- return this.isConfigured() && !!this.getAuthToken();
49
+ return true;
45
50
  }
46
51
  isOAuthRequest() {
47
- if (this._server?.getEnv("api_token", this) || this._server?.getEnv(API_KEY_HEADER)) {
52
+ if (getRequestHeader(REFLECT_API_TOKEN_HEADER) || getRequestHeader(API_KEY_HEADER)) {
53
+ return false;
54
+ }
55
+ const authHeader = getRequestHeader(AUTHORIZATION_HEADER);
56
+ if (!authHeader) {
48
57
  return false;
49
58
  }
50
- return !!this._server?.getEnv(AUTHORIZATION_HEADER);
59
+ const headerValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;
60
+ return headerValue.toLowerCase().startsWith("bearer ");
51
61
  }
52
62
  getAuthHeader() {
53
63
  const token = this.getAuthToken();
@@ -63,7 +73,7 @@ class ReflectClient {
63
73
  return {
64
74
  ...this.getAuthHeader(),
65
75
  "Content-Type": "application/json",
66
- "User-Agent": `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`
76
+ "User-Agent": USER_AGENT
67
77
  };
68
78
  }
69
79
  getSessionState(sessionId) {
@@ -111,6 +121,7 @@ class ReflectClient {
111
121
  new ExecuteSuite(this),
112
122
  new CancelSuiteExecution(this),
113
123
  new ListTests(this),
124
+ new GetTestDetail(this),
114
125
  new RunTest(this),
115
126
  new GetTestStatus(this)
116
127
  ];
@@ -1,4 +1,5 @@
1
1
  const API_KEY_HEADER = "X-API-KEY";
2
+ const REFLECT_API_TOKEN_HEADER = "Reflect-Api-Token";
2
3
  const AUTHORIZATION_HEADER = "Authorization";
3
4
  const API_HOSTNAME = "api.reflect.run";
4
5
  const WEBSOCKET_HOSTNAME = "recording.us-east-1.reflect.run";
@@ -7,6 +8,7 @@ export {
7
8
  API_HOSTNAME,
8
9
  API_KEY_HEADER,
9
10
  AUTHORIZATION_HEADER,
11
+ REFLECT_API_TOKEN_HEADER,
10
12
  WEBSOCKET_HOSTNAME,
11
13
  WEB_APP_HOSTNAME
12
14
  };
@@ -0,0 +1,37 @@
1
+ import { z } from "zod";
2
+ import { Tool, ToolError } from "../../../common/tools.js";
3
+ import { API_HOSTNAME } from "../../config/constants.js";
4
+ class GetTestDetail extends Tool {
5
+ specification = {
6
+ title: "Get Test Detail",
7
+ toolset: "Tests",
8
+ summary: "Get the full detail of a reflect test, including its name, description, and all recorded steps",
9
+ inputSchema: z.object({
10
+ testId: z.string().describe("ID of the reflect test to retrieve details for")
11
+ })
12
+ };
13
+ handle = async (args) => {
14
+ const { testId } = args;
15
+ if (!testId || !testId.trim())
16
+ throw new ToolError("testId argument is required");
17
+ const response = await fetch(
18
+ `https://${API_HOSTNAME}/v1/tests/${encodeURIComponent(testId)}`,
19
+ {
20
+ method: "GET",
21
+ headers: this.client.getHeaders()
22
+ }
23
+ );
24
+ if (!response.ok) {
25
+ throw new ToolError(
26
+ `Failed to get test detail: ${response.status} ${response.statusText}`
27
+ );
28
+ }
29
+ const data = await response.json();
30
+ return {
31
+ content: [{ type: "text", text: JSON.stringify(data) }]
32
+ };
33
+ };
34
+ }
35
+ export {
36
+ GetTestDetail
37
+ };
@@ -0,0 +1,36 @@
1
+ import { ToolError } from "../../common/tools.js";
2
+ const API_HOSTNAME = "api.reflect.run";
3
+ const FUNCTIONAL_TESTING_API_KEY_HEADER = "X-API-KEY";
4
+ class FunctionalTestingAPI {
5
+ constructor(getToken, userAgent) {
6
+ this.getToken = getToken;
7
+ this.userAgent = userAgent;
8
+ }
9
+ getFtHeaders() {
10
+ const token = this.getToken();
11
+ if (!token) {
12
+ throw new ToolError("Swagger Functional Testing API token not found");
13
+ }
14
+ return {
15
+ [FUNCTIONAL_TESTING_API_KEY_HEADER]: token,
16
+ "Content-Type": "application/json",
17
+ "User-Agent": this.userAgent
18
+ };
19
+ }
20
+ async listTests() {
21
+ const response = await fetch(`https://${API_HOSTNAME}/v1/tests`, {
22
+ method: "GET",
23
+ headers: this.getFtHeaders()
24
+ });
25
+ if (!response.ok) {
26
+ throw new ToolError(
27
+ `Failed to list Functional Testing tests: ${response.status} ${response.statusText}`
28
+ );
29
+ }
30
+ return response.json();
31
+ }
32
+ }
33
+ export {
34
+ FUNCTIONAL_TESTING_API_KEY_HEADER,
35
+ FunctionalTestingAPI
36
+ };
@@ -0,0 +1,11 @@
1
+ const FUNCTIONAL_TESTING_TOOLS = [
2
+ {
3
+ title: "List Tests",
4
+ toolset: "Functional Testing",
5
+ 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.",
6
+ handler: "listFunctionalTestingTests"
7
+ }
8
+ ];
9
+ export {
10
+ FUNCTIONAL_TESTING_TOOLS
11
+ };
@@ -1,3 +1,4 @@
1
+ import { FUNCTIONAL_TESTING_TOOLS } from "./functional-testing-tools.js";
1
2
  import { CreatePortalArgsSchema, PortalArgsSchema, UpdatePortalArgsSchema, CreateProductArgsSchema, ProductArgsSchema, UpdateProductArgsSchema, PublishProductArgsSchema, GetProductSectionsArgsSchema, CreateTableOfContentsArgsSchema, GetTableOfContentsArgsSchema, DeleteTableOfContentsArgsSchema, GetDocumentArgsSchema, UpdateDocumentArgsSchema } from "./portal-types.js";
2
3
  import { ApiSearchParamsSchema, ApiDefinitionParamsSchema, CreateApiParamsSchema, ScanStandardizationParamsSchema, CreateApiFromPromptParamsSchema, StandardizeApiParamsSchema } from "./registry-types.js";
3
4
  import { OrganizationsQuerySchema } from "./user-management-types.js";
@@ -165,7 +166,8 @@ const TOOLS = [
165
166
  summary: "Standardize and fix an API definition using AI to ensure compliance with governance policies. Scans the API definition for standardization errors and automatically fixes them using SmartBear AI. Optionally provide 'newVersion' (e.g. patch bump '1.0.0' → '1.0.1') to save the fixed definition as a new version — omitting it will overwrite the current version. Returns the number of errors found and the fixed definition if successful. Use this tool when users ask to standardize, fix, govern, or ensure governance compliance of APIs.",
166
167
  inputSchema: StandardizeApiParamsSchema,
167
168
  handler: "standardizeApi"
168
- }
169
+ },
170
+ ...FUNCTIONAL_TESTING_TOOLS
169
171
  ];
170
172
  export {
171
173
  TOOLS
@@ -1,6 +1,9 @@
1
1
  import { z } from "zod";
2
- import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info.js";
2
+ import { USER_AGENT } from "../common/info.js";
3
+ import { getRequestHeader } from "../common/request-context.js";
4
+ import { ToolError } from "../common/tools.js";
3
5
  import "./config-utils.js";
6
+ import { FunctionalTestingAPI, FUNCTIONAL_TESTING_API_KEY_HEADER } from "./client/functional-testing-api.js";
4
7
  import { SwaggerAPI } from "./client/api.js";
5
8
  import { SwaggerConfiguration } from "./client/configuration.js";
6
9
  import "./client/portal-types.js";
@@ -8,45 +11,69 @@ import "./client/registry-types.js";
8
11
  import { TOOLS } from "./client/tools.js";
9
12
  import "./client/user-management-types.js";
10
13
  const ConfigurationSchema = z.object({
14
+ api_key: z.string().optional().describe("Swagger API key for authentication"),
11
15
  portal_base_path: z.string().optional().describe("Base path for Portal API requests (optional)"),
12
16
  registry_base_path: z.string().optional().describe("Base path for Registry API requests (optional)"),
13
- ui_base_path: z.string().optional().describe("Base URL for the SwaggerHub UI (optional)")
14
- });
15
- const AuthenticationSchema = z.object({
16
- api_key: z.string().describe("Swagger API key for authentication").optional()
17
+ ui_base_path: z.string().optional().describe("Base URL for the SwaggerHub UI (optional)"),
18
+ functional_testing_api_token: z.string().optional().describe(
19
+ "Swagger Functional Testing API token. Leave empty to disable Functional Testing tools."
20
+ )
17
21
  });
18
22
  class SwaggerClient {
19
- apiConfig;
20
- server;
23
+ api;
24
+ _apiKey;
25
+ ftApi;
26
+ _ftApiToken;
21
27
  name = "Swagger";
22
28
  capabilityPrefix = "swagger";
23
29
  configPrefix = "Swagger";
24
30
  config = ConfigurationSchema;
25
- authenticationFields = AuthenticationSchema;
26
- async configure(server, config) {
27
- this.server = server;
28
- this.apiConfig = new SwaggerConfiguration({
29
- token: () => this.getAuthToken(),
30
- portalBasePath: config.portal_base_path,
31
- registryBasePath: config.registry_base_path,
32
- uiBasePath: config.ui_base_path
33
- });
34
- }
35
- isConfigured() {
36
- return this.apiConfig !== void 0;
31
+ async configure(_server, config, _cache) {
32
+ if (config.api_key) {
33
+ this._apiKey = config.api_key;
34
+ this.api = new SwaggerAPI(
35
+ new SwaggerConfiguration({
36
+ token: () => this.getAuthToken(),
37
+ portalBasePath: config.portal_base_path,
38
+ registryBasePath: config.registry_base_path,
39
+ uiBasePath: config.ui_base_path
40
+ }),
41
+ USER_AGENT
42
+ );
43
+ }
44
+ if (config.functional_testing_api_token) {
45
+ this._ftApiToken = config.functional_testing_api_token;
46
+ this.ftApi = new FunctionalTestingAPI(
47
+ () => this.getFtAuthToken(),
48
+ USER_AGENT
49
+ );
50
+ }
37
51
  }
38
52
  getAuthToken() {
39
- return this.server?.getEnv("api_key", this) || this.server?.getEnv("Authorization") || null;
53
+ const contextHeader = getRequestHeader("Swagger-Api-Key") || getRequestHeader("Authorization");
54
+ if (contextHeader) {
55
+ let token = Array.isArray(contextHeader) ? contextHeader[0] : contextHeader;
56
+ if (token.startsWith("Bearer ")) {
57
+ token = token.substring(7);
58
+ }
59
+ return token;
60
+ }
61
+ return this._apiKey || null;
62
+ }
63
+ getFtAuthToken() {
64
+ if (!this.ftApi) return null;
65
+ const contextHeader = getRequestHeader(FUNCTIONAL_TESTING_API_KEY_HEADER);
66
+ if (contextHeader) {
67
+ return Array.isArray(contextHeader) ? contextHeader[0] : contextHeader;
68
+ }
69
+ return this._ftApiToken || null;
40
70
  }
41
- hasAuth() {
42
- return this.isConfigured() && !!this.getAuthToken();
71
+ isConfigured() {
72
+ return this.api !== void 0 || this.ftApi !== void 0;
43
73
  }
44
74
  getApi() {
45
- if (!this.apiConfig) throw new Error("Client not configured");
46
- return new SwaggerAPI(
47
- this.apiConfig,
48
- `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`
49
- );
75
+ if (!this.api) throw new Error("Client not configured");
76
+ return this.api;
50
77
  }
51
78
  // Delegate API methods to the SwaggerAPI instance
52
79
  async getPortals() {
@@ -126,8 +153,20 @@ class SwaggerClient {
126
153
  async standardizeApi(args) {
127
154
  return this.getApi().standardizeApi(args);
128
155
  }
156
+ async listFunctionalTestingTests() {
157
+ if (!this.ftApi) {
158
+ throw new ToolError("Functional Testing API not configured");
159
+ }
160
+ return this.ftApi.listTests();
161
+ }
129
162
  async registerTools(register, _getInput) {
130
163
  TOOLS.forEach((tool) => {
164
+ if (tool.toolset === "Functional Testing" && !this.ftApi) {
165
+ return;
166
+ }
167
+ if (tool.toolset !== "Functional Testing" && !this.api && this.isConfigured()) {
168
+ return;
169
+ }
131
170
  const { handler, formatResponse, ...toolParams } = tool;
132
171
  register(toolParams, async (args, _extra) => {
133
172
  try {
@@ -1,4 +1,5 @@
1
1
  import zod__default from "zod";
2
+ import { getRequestHeader } from "../common/request-context.js";
2
3
  import { ApiClient } from "./common/api-client.js";
3
4
  import { GetEnvironments } from "./tool/environment/get-environments.js";
4
5
  import { CreateFolder } from "./tool/folder/create-folder.js";
@@ -35,32 +36,38 @@ import { GetTestExecutions } from "./tool/test-execution/get-test-executions.js"
35
36
  import { GetTestExecutionSteps } from "./tool/test-execution/get-test-steps.js";
36
37
  import { UpdateTestExecution } from "./tool/test-execution/update-test-execution.js";
37
38
  import { UpdateTestExecutionSteps } from "./tool/test-execution/update-test-steps.js";
39
+ const BASE_URL_DEFAULT = "https://api.zephyrscale.smartbear.com/v2";
38
40
  const ConfigurationSchema = zod__default.object({
39
- base_url: zod__default.url().optional().describe("Zephyr Scale API base URL").default("https://api.zephyrscale.smartbear.com/v2")
40
- });
41
- const AuthenticationSchema = zod__default.object({
42
- api_token: zod__default.string().describe("Zephyr Scale API token for authentication").optional()
41
+ api_token: zod__default.string().describe("Zephyr Scale API token for authentication"),
42
+ base_url: zod__default.string().url().optional().describe("Zephyr Scale API base URL").default(BASE_URL_DEFAULT)
43
43
  });
44
44
  class ZephyrClient {
45
45
  apiClient;
46
- server;
46
+ _apiToken;
47
47
  name = "Zephyr";
48
48
  capabilityPrefix = "zephyr";
49
49
  configPrefix = "Zephyr";
50
50
  config = ConfigurationSchema;
51
- authenticationFields = AuthenticationSchema;
52
- async configure(server, config) {
53
- this.server = server;
54
- this.apiClient = new ApiClient(() => this.getAuthToken(), config.base_url);
51
+ async configure(_server, config, _cache) {
52
+ this._apiToken = config.api_token;
53
+ this.apiClient = new ApiClient(
54
+ () => this.getAuthToken(),
55
+ config.base_url || process.env.ZEPHYR_CUSTOM_BASE_URL || BASE_URL_DEFAULT
56
+ );
55
57
  }
56
58
  getAuthToken() {
57
- return this.server?.getEnv("api_token", this) || this.server?.getEnv("Authorization") || null;
59
+ const contextHeader = getRequestHeader("Zephyr-Api-Token") || getRequestHeader("Authorization");
60
+ if (contextHeader) {
61
+ let token = Array.isArray(contextHeader) ? contextHeader[0] : contextHeader;
62
+ if (token.startsWith("Bearer ")) {
63
+ token = token.substring(7);
64
+ }
65
+ return token;
66
+ }
67
+ return this._apiToken || null;
58
68
  }
59
69
  isConfigured() {
60
- return !!this.apiClient;
61
- }
62
- hasAuth() {
63
- return this.isConfigured() && !!this.getAuthToken();
70
+ return this.apiClient !== void 0;
64
71
  }
65
72
  getApiClient() {
66
73
  if (!this.apiClient) throw new Error("Client not configured");
@@ -1,4 +1,4 @@
1
- import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../../common/info.js";
1
+ import { USER_AGENT } 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": `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`,
11
+ "User-Agent": USER_AGENT,
12
12
  "zscale-source": "smartbear-mcp"
13
13
  };
14
14
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartbear/mcp",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "MCP server for interacting SmartBear Products",
5
5
  "keywords": [
6
6
  "smartbear",
@@ -51,6 +51,7 @@
51
51
  "dependencies": {
52
52
  "@bugsnag/js": "^8.8.1",
53
53
  "@modelcontextprotocol/sdk": "^1.27.1",
54
+ "js-yaml": "^4.2.0",
54
55
  "node-cache": "^5.1.2",
55
56
  "swagger-client": "^3.37.1",
56
57
  "vite": "^7.3.1",