@smartbear/mcp 0.10.0 → 0.12.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 (63) hide show
  1. package/README.md +10 -8
  2. package/dist/bugsnag/client/api/Error.js +1 -1
  3. package/dist/bugsnag/client/api/Project.js +152 -0
  4. package/dist/bugsnag/client/api/api.js +130 -3
  5. package/dist/bugsnag/client/api/base.js +19 -0
  6. package/dist/bugsnag/client/api/index.js +2 -0
  7. package/dist/bugsnag/client/filters.js +0 -6
  8. package/dist/bugsnag/client.js +739 -378
  9. package/dist/bugsnag/input-schemas.js +59 -0
  10. package/dist/collaborator/client.js +18 -5
  11. package/dist/common/cache.js +63 -0
  12. package/dist/common/client-registry.js +128 -0
  13. package/dist/common/register-clients.js +31 -0
  14. package/dist/common/server.js +30 -9
  15. package/dist/common/transport-http.js +377 -0
  16. package/dist/common/transport-stdio.js +48 -0
  17. package/dist/index.js +20 -70
  18. package/dist/pactflow/client.js +39 -19
  19. package/dist/qmetry/client/auto-resolve.js +10 -0
  20. package/dist/qmetry/client/automation.js +171 -0
  21. package/dist/qmetry/client/handlers.js +9 -2
  22. package/dist/qmetry/client/project.js +87 -1
  23. package/dist/qmetry/client/testsuite.js +37 -1
  24. package/dist/qmetry/client/tools/automation-tools.js +290 -0
  25. package/dist/qmetry/client/tools/index.js +3 -0
  26. package/dist/qmetry/client/tools/issue-tools.js +1 -1
  27. package/dist/qmetry/client/tools/project-tools.js +311 -1
  28. package/dist/qmetry/client/tools/requirement-tools.js +1 -1
  29. package/dist/qmetry/client/tools/testcase-tools.js +311 -39
  30. package/dist/qmetry/client/tools/testsuite-tools.js +337 -23
  31. package/dist/qmetry/client.js +24 -9
  32. package/dist/qmetry/config/constants.js +6 -0
  33. package/dist/qmetry/config/rest-endpoints.js +8 -0
  34. package/dist/qmetry/types/automation.js +8 -0
  35. package/dist/qmetry/types/common.js +299 -4
  36. package/dist/qmetry/types/issues.js +5 -0
  37. package/dist/qmetry/types/project.js +13 -0
  38. package/dist/qmetry/types/requirements.js +5 -0
  39. package/dist/qmetry/types/testcase.js +5 -0
  40. package/dist/qmetry/types/testsuite.js +9 -0
  41. package/dist/reflect/client.js +10 -4
  42. package/dist/{api-hub → swagger}/client/api.js +94 -37
  43. package/dist/{api-hub → swagger}/client/configuration.js +4 -2
  44. package/dist/{api-hub → swagger}/client/index.js +2 -2
  45. package/dist/{api-hub → swagger}/client/portal-types.js +7 -6
  46. package/dist/{api-hub → swagger}/client/registry-types.js +26 -0
  47. package/dist/{api-hub → swagger}/client/tools.js +19 -20
  48. package/dist/{api-hub → swagger}/client.js +51 -39
  49. package/dist/swagger/config-utils.js +18 -0
  50. package/dist/tests/unit/bugsnag/utils/factories.js +86 -0
  51. package/dist/zephyr/client.js +44 -8
  52. package/dist/zephyr/common/rest-api-schemas.js +79 -78
  53. package/dist/zephyr/tool/environment/get-environments.js +68 -0
  54. package/dist/zephyr/tool/priority/get-priorities.js +43 -0
  55. package/dist/zephyr/tool/status/get-statuses.js +49 -0
  56. package/dist/zephyr/tool/test-case/get-test-case.js +39 -0
  57. package/dist/zephyr/tool/test-case/get-test-cases.js +64 -0
  58. package/dist/zephyr/tool/test-cycle/get-test-cycle.js +39 -0
  59. package/dist/zephyr/tool/test-cycle/get-test-cycles.js +2 -2
  60. package/dist/zephyr/tool/test-execution/get-test-execution.js +39 -0
  61. package/dist/zephyr/tool/test-execution/get-test-executions.js +45 -0
  62. package/package.json +4 -3
  63. /package/dist/{api-hub → swagger}/client/user-management-types.js +0 -0
@@ -21,6 +21,25 @@ export const DEFAULT_FOLDER_OPTIONS = {
21
21
  restoreDefaultColumns: false,
22
22
  folderID: null,
23
23
  };
24
+ /**
25
+ * Entity types supported for automation result import
26
+ */
27
+ export const EntityTypeEnum = z.enum([
28
+ "TESTNG",
29
+ "CUCUMBER",
30
+ "JUNIT",
31
+ "HPUFT",
32
+ "QAF",
33
+ "ROBOT",
34
+ ]);
35
+ /**
36
+ * Automation hierarchy options for TestNG and JUnit
37
+ */
38
+ export const AutomationHierarchyEnum = z.enum(["1", "2", "3"]);
39
+ /**
40
+ * Skip warning options
41
+ */
42
+ export const SkipWarningEnum = z.enum(["0", "1"]);
24
43
  // Reusable Zod schema components
25
44
  export const CommonFields = {
26
45
  projectKey: z
@@ -288,12 +307,94 @@ export const PlatformArgsSchema = z.object({
288
307
  .default('[{"property":"platformID","direction":"DESC"}]'),
289
308
  filter: CommonFields.filter,
290
309
  });
310
+ export const CreateReleaseArgsSchema = z.object({
311
+ projectKey: CommonFields.projectKeyOptional,
312
+ baseUrl: CommonFields.baseUrl,
313
+ release: z.object({
314
+ name: z.string().describe("Release name (required)"),
315
+ description: z.string().optional().describe("Release description"),
316
+ startDate: z
317
+ .string()
318
+ .optional()
319
+ .describe("Release start date in format DD-MM-YYYY or MM-DD-YYYY (depends on QMetry instance date format configuration)"),
320
+ targetDate: z
321
+ .string()
322
+ .optional()
323
+ .describe("Release target/end date in format DD-MM-YYYY or MM-DD-YYYY (depends on QMetry instance date format configuration)"),
324
+ projectID: z
325
+ .number()
326
+ .optional()
327
+ .describe("Project ID (optional, can be auto-resolved from project key if not provided)"),
328
+ }),
329
+ cycle: z
330
+ .object({
331
+ name: z.string().describe("Cycle name (required if cycle is provided)"),
332
+ isLocked: z
333
+ .boolean()
334
+ .optional()
335
+ .describe("Whether the cycle is locked (default: false)"),
336
+ isArchived: z
337
+ .boolean()
338
+ .optional()
339
+ .describe("Whether the cycle is archived (default: false)"),
340
+ })
341
+ .optional()
342
+ .describe("Optional cycle to create within the release"),
343
+ });
344
+ export const CreateCycleArgsSchema = z.object({
345
+ projectKey: CommonFields.projectKeyOptional,
346
+ baseUrl: CommonFields.baseUrl,
347
+ cycle: z.object({
348
+ name: z.string().describe("Cycle name (required)"),
349
+ startDate: z
350
+ .string()
351
+ .optional()
352
+ .describe("Cycle start date in format DD-MM-YYYY or MM-DD-YYYY (depends on QMetry instance date format configuration)"),
353
+ targetDate: z
354
+ .string()
355
+ .optional()
356
+ .describe("Cycle target/end date in format DD-MM-YYYY or MM-DD-YYYY (depends on QMetry instance date format configuration)"),
357
+ projectID: z
358
+ .number()
359
+ .optional()
360
+ .describe("Project ID (optional, can be auto-resolved from project key if not provided)"),
361
+ releaseID: z
362
+ .number()
363
+ .describe("Release ID (required) - the release this cycle belongs to"),
364
+ }),
365
+ });
366
+ export const UpdateCycleArgsSchema = z.object({
367
+ projectKey: CommonFields.projectKeyOptional,
368
+ baseUrl: CommonFields.baseUrl,
369
+ cycle: z.object({
370
+ name: z.string().optional().describe("Cycle name (optional for update)"),
371
+ startDate: z
372
+ .string()
373
+ .optional()
374
+ .describe("Cycle start date in format DD-MM-YYYY or MM-DD-YYYY (depends on QMetry instance date format configuration)"),
375
+ targetDate: z
376
+ .string()
377
+ .optional()
378
+ .describe("Cycle target/end date in format DD-MM-YYYY or MM-DD-YYYY (depends on QMetry instance date format configuration)"),
379
+ buildID: z
380
+ .number()
381
+ .describe("Build ID (required for identifying the cycle to update). " +
382
+ "To get the buildID - Call API 'Cycle/List' (FETCH_RELEASES_CYCLES tool). " +
383
+ "From the response, get value of following attribute -> data[<index>].buildID"),
384
+ releaseID: z
385
+ .number()
386
+ .describe("Release ID (required for identifying the cycle to update). " +
387
+ "To get the releaseID - Call API 'Cycle/List' (FETCH_RELEASES_CYCLES tool). " +
388
+ "From the response, get value of following attribute -> data[<index>].releaseID"),
389
+ }),
390
+ });
291
391
  export const CreateTestCaseStepSchema = z.object({
292
392
  orderId: z.number(),
293
393
  description: z.string(),
294
394
  inputData: z.string().optional(),
295
395
  expectedOutcome: z.string().optional(),
296
396
  UDF: z.record(z.string()).optional(),
397
+ tcStepID: z.number().optional(), // Required for updating existing steps, omit for new steps
297
398
  });
298
399
  export const UpdateTestCaseRemoveStepSchema = z.object({
299
400
  tcID: z.number(),
@@ -336,9 +437,40 @@ export const UpdateTestCaseArgsSchema = z.object({
336
437
  baseUrl: CommonFields.baseUrl,
337
438
  tcID: CommonFields.tcID,
338
439
  tcVersionID: CommonFields.tcVersionID,
339
- withVersion: z.boolean().optional(),
340
- notrunall: z.boolean().optional(),
341
- isStepUpdated: z.boolean().optional(),
440
+ tcVersion: z
441
+ .number()
442
+ .optional()
443
+ .describe("Test Case version number (required when withVersion=true for creating new version). " +
444
+ "This is the current version number from which a new version will be created."),
445
+ withVersion: z
446
+ .boolean()
447
+ .optional()
448
+ .describe("Pass 'true' if you want to create a new version of the test case with incremented version number. " +
449
+ "When true, a new version is created (e.g., if current version is 2, new version 3 is created). " +
450
+ "When false or omitted, updates the existing version specified by tcVersionID. " +
451
+ "IMPORTANT: Always send proper tcVersionID to identify which version the request is for."),
452
+ versionComment: z
453
+ .string()
454
+ .optional()
455
+ .describe("Comment or description for the new version (used only when withVersion=true). " +
456
+ "Helps track what changed in this new version. Example: 'Updated test steps for new requirements'"),
457
+ notruncurrent: z
458
+ .boolean()
459
+ .optional()
460
+ .describe("Flag to control execution behavior for current version when creating a new version. " +
461
+ "Used in conjunction with withVersion=true."),
462
+ notrunall: z
463
+ .boolean()
464
+ .optional()
465
+ .describe("Flag to control execution behavior for all versions when creating a new version. " +
466
+ "Used in conjunction with withVersion=true."),
467
+ folderPath: CommonFields.tsFolderPath,
468
+ scope: CommonFields.scope,
469
+ isStepUpdated: z
470
+ .boolean()
471
+ .optional()
472
+ .describe("Set to true when steps are being added, updated, or removed. " +
473
+ "Required when including 'steps' or 'removeSteps' arrays."),
342
474
  steps: z.array(CreateTestCaseStepSchema).optional(),
343
475
  removeSteps: z.array(UpdateTestCaseRemoveStepSchema).optional(),
344
476
  name: z.string().optional(),
@@ -347,10 +479,18 @@ export const UpdateTestCaseArgsSchema = z.object({
347
479
  owner: z.number().optional(),
348
480
  testCaseState: z.number().optional(),
349
481
  testCaseType: z.number().optional(),
482
+ estimatedTime: z
483
+ .number()
484
+ .optional()
485
+ .describe("Estimated execution time in seconds. Example: 7200 for 2 hours"),
350
486
  executionMinutes: z.number().optional(),
351
487
  testingType: z.number().optional(),
352
488
  description: z.string().optional(),
353
- updateOnlyMetadata: z.boolean().optional(),
489
+ updateOnlyMetadata: z
490
+ .boolean()
491
+ .optional()
492
+ .describe("Set to true to update only metadata fields without touching test steps. " +
493
+ "When true, steps and removeSteps are ignored."),
354
494
  });
355
495
  export const TestCaseListArgsSchema = z.object({
356
496
  projectKey: CommonFields.projectKeyOptional,
@@ -762,3 +902,158 @@ export const LinkPlatformsToTestSuiteArgsSchema = z.object({
762
902
  .string()
763
903
  .describe("Comma-separated value of PlatformId (required). To get the qmPlatformId - Call API 'Platform/List' From the response, get value of following attribute -> data[<index>].platformID"),
764
904
  });
905
+ // Export for Bulk Update Test Case Execution Status tool
906
+ export const BulkUpdateExecutionStatusArgsSchema = z.object({
907
+ projectKey: CommonFields.projectKeyOptional,
908
+ baseUrl: CommonFields.baseUrl,
909
+ entityIDs: z
910
+ .string()
911
+ .describe("Comma-separated IDs of Test Case Runs to update (e.g., '66095087' for single or '66095069,66095075' for bulk). " +
912
+ "To get the entityIDs - Call API 'Execution/Fetch Testcase Run ID' " +
913
+ "From the response, get value of following attribute -> data[<index>].tcRunID"),
914
+ entityType: z
915
+ .enum(["TCR", "TCSR"])
916
+ .describe("Type of Entity to Execute: 'TCR' (Test Case Run) or 'TCSR' (Test Case Step Run)")
917
+ .default("TCR"),
918
+ qmTsRunId: z
919
+ .string()
920
+ .describe("Id of Test Suite Run to execute (required). " +
921
+ "To get the qmTsRunId - Call API 'Execution/Fetch Executions' " +
922
+ "From the response, get value of following attribute -> data[<index>].tsRunID"),
923
+ runStatusID: z
924
+ .number()
925
+ .describe("Id of the execution status to set (required). " +
926
+ "To get the runStatusID - Call API 'Admin/Project GET info Service' " +
927
+ "From the response, get value of following attribute -> allstatus[<index>].id " +
928
+ "Common statuses: Pass, Fail, Not Run, Blocked, WIP, etc."),
929
+ dropID: z
930
+ .union([z.number(), z.string()])
931
+ .optional()
932
+ .describe("Unique identifier of drop/build on which execution is to be performed (optional). " +
933
+ "To get the dropID - Call API 'Fetch Build/List' " +
934
+ "From the response, get value of following attribute -> data[<index>].dropID"),
935
+ isAutoExecuted: z
936
+ .enum(["0", "1"])
937
+ .optional()
938
+ .describe("Set '1' for automated and '0' for manual Execution Type"),
939
+ isBulkOperation: z
940
+ .boolean()
941
+ .optional()
942
+ .describe("Set true for bulk operations (multiple entityIDs), false for single execution update. " +
943
+ "Default: true if multiple comma-separated entityIDs, false otherwise"),
944
+ comments: z
945
+ .string()
946
+ .optional()
947
+ .describe("Optional comments for the execution status update"),
948
+ username: z
949
+ .string()
950
+ .optional()
951
+ .describe("If Part 11 Compliance is active then required for authentication"),
952
+ password: z
953
+ .string()
954
+ .optional()
955
+ .describe("If Part 11 Compliance is active then required for authentication"),
956
+ qmRunObj: z
957
+ .string()
958
+ .optional()
959
+ .describe("Internal QMetry run object (optional, usually empty string)"),
960
+ type: z
961
+ .enum(["TCR", "TCSR"])
962
+ .optional()
963
+ .describe("Type of Entity - same as entityType (for backwards compatibility)"),
964
+ });
965
+ /**
966
+ * Import automation results payload schema
967
+ *
968
+ * CRITICAL: File upload is required and must be provided by the user
969
+ * User should upload a valid result file (.json, .xml, or .zip up to 30 MB)
970
+ */
971
+ export const ImportAutomationResultsPayloadSchema = z.object({
972
+ // REQUIRED: File data as base64 string or file path
973
+ file: z
974
+ .string()
975
+ .refine((val) => {
976
+ // Base64 regex: matches typical base64 strings (not perfect, but covers most cases)
977
+ const base64Regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
978
+ // File path regex: ends with .json, .xml, or .zip (case-insensitive)
979
+ const filePathRegex = /\.(json|xml|zip)$/i;
980
+ return base64Regex.test(val) || filePathRegex.test(val);
981
+ }, {
982
+ message: "Must be a valid base64 string or a file path ending with .json, .xml, or .zip",
983
+ })
984
+ .describe("Base64 encoded file content or file path. User must upload result file (.json, .xml, .zip up to 30 MB)"),
985
+ // REQUIRED: Original filename with extension
986
+ fileName: z
987
+ .string()
988
+ .describe("Original filename with extension (.json, .xml, or .zip)"),
989
+ // REQUIRED: Entity type (format of result file)
990
+ entityType: EntityTypeEnum.describe("Format of result file: TESTNG, CUCUMBER, JUNIT, HPUFT, QAF, or ROBOT"),
991
+ // OPTIONAL: Automation hierarchy (applies to TestNG and JUnit only)
992
+ automationHierarchy: AutomationHierarchyEnum.optional().describe("TestNG/JUnit hierarchy: 1=Test Case-Test Step, 2=Test Case only, 3=Test Suite-Test Case. Default: 1"),
993
+ // OPTIONAL: Test suite name
994
+ testsuiteName: z
995
+ .string()
996
+ .optional()
997
+ .describe("Custom test suite name. Ignored if automationHierarchy=3 for JUnit or =2 for ROBOT"),
998
+ // OPTIONAL: Test suite ID (reuse existing)
999
+ testsuiteId: z
1000
+ .string()
1001
+ .optional()
1002
+ .describe("Reuse existing Test Suite by ID or Entity Key. Ignored if automationHierarchy=3 for JUnit or =2 for ROBOT"),
1003
+ // OPTIONAL: Test suite folder path
1004
+ tsFolderPath: z
1005
+ .string()
1006
+ .optional()
1007
+ .describe("Test suite folder path. Creates folder if doesn't exist. Ignored if reusing test suite"),
1008
+ // OPTIONAL: Test case folder path
1009
+ tcFolderPath: z
1010
+ .string()
1011
+ .optional()
1012
+ .describe("Test case folder path. Creates folder if doesn't exist. Ignored if reusing test case"),
1013
+ // OPTIONAL: Platform ID or name
1014
+ platformID: z
1015
+ .string()
1016
+ .optional()
1017
+ .describe("Platform ID or Platform Name. Default: 'No Platform'"),
1018
+ // OPTIONAL: Project ID or key (overrides header project)
1019
+ projectID: z
1020
+ .string()
1021
+ .optional()
1022
+ .describe("Project ID, Project Key, or Project name. Overrides project in header"),
1023
+ // OPTIONAL: Release ID or name
1024
+ releaseID: z
1025
+ .string()
1026
+ .optional()
1027
+ .describe("Release ID or Release name. Requires projectID if provided"),
1028
+ // OPTIONAL: Cycle ID or name
1029
+ cycleID: z
1030
+ .string()
1031
+ .optional()
1032
+ .describe("Cycle ID or Cycle name. Requires releaseID and projectID if provided"),
1033
+ // OPTIONAL: Build ID or name
1034
+ buildID: z.string().optional().describe("Build ID or Build name"),
1035
+ // OPTIONAL: Test case fields (JSON format)
1036
+ testcase_fields: z
1037
+ .string()
1038
+ .optional()
1039
+ .describe('JSON string with test case system fields and UDFs. Ignored if reusing test case. Example: {"component":["com1"], "priority":"High"}'),
1040
+ // OPTIONAL: Test suite fields (JSON format)
1041
+ testsuite_fields: z
1042
+ .string()
1043
+ .optional()
1044
+ .describe('JSON string with test suite system fields and UDFs. Ignored if reusing test suite. Example: {"testSuiteState":"Open", "testsuiteOwner":"user"}'),
1045
+ // OPTIONAL: Skip warning about summary length
1046
+ skipWarning: SkipWarningEnum.optional().describe("0=Fail if summary >255 chars, 1=Truncate summary to 255 chars. Default: 0"),
1047
+ // OPTIONAL: Matching requirement for test cases
1048
+ is_matching_required: z
1049
+ .string()
1050
+ .optional()
1051
+ .describe("True=Create new TC if summary/steps don't match, False=Reuse linked TC. Default: True"),
1052
+ });
1053
+ export const FetchAutomationStatusPayloadSchema = z.object({
1054
+ projectKey: CommonFields.projectKeyOptional,
1055
+ baseUrl: CommonFields.baseUrl,
1056
+ requestID: z
1057
+ .number()
1058
+ .describe("Numeric request ID from import automation response"),
1059
+ });
@@ -13,4 +13,9 @@ export const DEFAULT_FETCH_ISSUES_PAYLOAD = {
13
13
  ...DEFAULT_SORT,
14
14
  udfFilter: "[]",
15
15
  isJiraIntegated: false,
16
+ /**
17
+ * Prevents filter persistence in the QMetry web application UI.
18
+ * Always set to false to ensure filters are not saved when fetching test cases via API.
19
+ */
20
+ isFilterSaveRequired: false,
16
21
  };
@@ -15,3 +15,16 @@ export const DEFAULT_FETCH_PLATFORMS_PAYLOAD = {
15
15
  ...DEFAULT_FILTER,
16
16
  ...DEFAULT_SORT,
17
17
  };
18
+ export const DEFAULT_CREATE_RELEASE_PAYLOAD = {
19
+ release: {
20
+ name: "Default Release",
21
+ },
22
+ cycle: {
23
+ name: "Default Cycle",
24
+ },
25
+ };
26
+ export const DEFAULT_CREATE_CYCLE_PAYLOAD = {
27
+ cycle: {
28
+ name: "Default Cycle",
29
+ },
30
+ };
@@ -7,6 +7,11 @@ export const DEFAULT_FETCH_REQUIREMENTS_PAYLOAD = {
7
7
  udfFilter: "[]",
8
8
  isJiraFilter: false,
9
9
  filterType: "QMETRY",
10
+ /**
11
+ * Prevents filter persistence in the QMetry web application UI.
12
+ * Always set to false to ensure filters are not saved when fetching test cases via API.
13
+ */
14
+ isFilterSaveRequired: false,
10
15
  };
11
16
  export const DEFAULT_FETCH_REQUIREMENT_DETAILS_PAYLOAD = {
12
17
  // No defaults needed for this simple payload
@@ -4,6 +4,11 @@ export const DEFAULT_FETCH_TESTCASES_PAYLOAD = {
4
4
  ...DEFAULT_FILTER,
5
5
  ...DEFAULT_FOLDER_OPTIONS,
6
6
  udfFilter: "[]",
7
+ /**
8
+ * Prevents filter persistence in the QMetry web application UI.
9
+ * Always set to false to ensure filters are not saved when fetching test cases via API.
10
+ */
11
+ isFilterSaveRequired: false,
7
12
  };
8
13
  export const DEFAULT_CREATE_TESTCASES_PAYLOAD = {
9
14
  scope: "project",
@@ -40,5 +40,14 @@ export const DEFAULT_FETCH_TESTSUITES_PAYLOAD = {
40
40
  ...DEFAULT_SORT,
41
41
  udfFilter: "[]",
42
42
  scope: "cycle",
43
+ /**
44
+ * Prevents filter persistence in the QMetry web application UI.
45
+ * Always set to false to ensure filters are not saved when fetching test cases via API.
46
+ */
47
+ isFilterSaveRequired: false,
43
48
  };
44
49
  export const DEFAULT_LINKED_PLATFORMS_TO_TESTSUITE_PAYLOAD = {};
50
+ export const DEFAULT_BULK_UPDATE_EXECUTION_STATUS_PAYLOAD = {
51
+ entityType: "TCR",
52
+ qmRunObj: "",
53
+ };
@@ -1,17 +1,23 @@
1
1
  import { z } from "zod";
2
2
  import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info.js";
3
3
  import { ToolError, } from "../common/types.js";
4
+ const ConfigurationSchema = z.object({
5
+ api_token: z.string().describe("Reflect API authentication token"),
6
+ });
4
7
  // ReflectClient class implementing the Client interface
5
8
  export class ReflectClient {
6
- headers;
9
+ headers = {};
7
10
  name = "Reflect";
8
- prefix = "reflect";
9
- constructor(token) {
11
+ toolPrefix = "reflect";
12
+ configPrefix = "Reflect";
13
+ config = ConfigurationSchema;
14
+ async configure(_server, config, _cache) {
10
15
  this.headers = {
11
- "X-API-KEY": `${token}`,
16
+ "X-API-KEY": `${config.api_token}`,
12
17
  "Content-Type": "application/json",
13
18
  "User-Agent": `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`,
14
19
  };
20
+ return true;
15
21
  }
16
22
  async listReflectSuites() {
17
23
  const response = await fetch("https://api.reflect.run/v1/suites", {
@@ -7,7 +7,25 @@ import { ToolError } from "../../common/types.js";
7
7
  // - group 3: name
8
8
  // - group 4: version
9
9
  const SWAGGER_URL_REGEX = /\/(apis|domains|templates)\/([^/]+)\/([^/]+)\/([^/]+)/;
10
- export class ApiHubAPI {
10
+ /**
11
+ * Type guard to check if a value has an 'id' property
12
+ */
13
+ function hasId(value) {
14
+ return typeof value === "object" && value !== null && "id" in value;
15
+ }
16
+ /**
17
+ * Type guard to check if a value has a 'message' property
18
+ */
19
+ function hasMessage(value) {
20
+ return typeof value === "object" && value !== null && "message" in value;
21
+ }
22
+ /**
23
+ * Type guard to check if a value has an 'errorsFound' property
24
+ */
25
+ function hasErrorsFound(value) {
26
+ return typeof value === "object" && value !== null && "errorsFound" in value;
27
+ }
28
+ export class SwaggerAPI {
11
29
  config;
12
30
  headers;
13
31
  constructor(config, userAgent) {
@@ -118,7 +136,7 @@ export class ApiHubAPI {
118
136
  body: JSON.stringify(body),
119
137
  });
120
138
  const result = await this.handleResponse(response);
121
- if (!("id" in result)) {
139
+ if (!hasId(result)) {
122
140
  throw new Error("Unexpected empty response creating portal");
123
141
  }
124
142
  return result;
@@ -129,21 +147,11 @@ export class ApiHubAPI {
129
147
  headers: this.headers,
130
148
  });
131
149
  const result = await this.handleResponse(response);
132
- if (!("id" in result)) {
150
+ if (!hasId(result)) {
133
151
  throw new ToolError("Portal not found or empty response");
134
152
  }
135
153
  return result;
136
154
  }
137
- async deletePortal(portalId) {
138
- const response = await fetch(`${this.config.portalBasePath}/portals/${portalId}`, {
139
- method: "DELETE",
140
- headers: this.headers,
141
- });
142
- if (!response.ok) {
143
- const errorText = await response.text().catch(() => "");
144
- throw new ToolError(`API Hub deletePortal failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
145
- }
146
- }
147
155
  async updatePortal(portalId, body) {
148
156
  const response = await fetch(`${this.config.portalBasePath}/portals/${portalId}`, {
149
157
  method: "PATCH",
@@ -167,7 +175,7 @@ export class ApiHubAPI {
167
175
  body: JSON.stringify(body),
168
176
  });
169
177
  const result = await this.handleResponse(response);
170
- if (!("id" in result)) {
178
+ if (!hasId(result)) {
171
179
  throw new Error("Unexpected empty response creating product");
172
180
  }
173
181
  return result;
@@ -178,7 +186,7 @@ export class ApiHubAPI {
178
186
  headers: this.headers,
179
187
  });
180
188
  const result = await this.handleResponse(response);
181
- if (!("id" in result)) {
189
+ if (!hasId(result)) {
182
190
  throw new ToolError("Product not found or empty response");
183
191
  }
184
192
  return result;
@@ -320,24 +328,6 @@ export class ApiHubAPI {
320
328
  }
321
329
  return { success: true };
322
330
  }
323
- /**
324
- * Delete document
325
- * @param args - Parameters for deleting document
326
- * @returns Success response
327
- */
328
- async deleteDocument(args) {
329
- const { documentId } = args;
330
- const url = `${this.config.portalBasePath}/documents/${documentId}`;
331
- const response = await fetch(url, {
332
- method: "DELETE",
333
- headers: this.headers,
334
- });
335
- if (!response.ok) {
336
- const errorText = await response.text();
337
- throw new Error(`API Hub deleteDocument failed - status: ${response.status} ${response.statusText}. Response: ${errorText}`);
338
- }
339
- return { success: true };
340
- }
341
331
  /**
342
332
  * Delete table of contents entry
343
333
  * @param args - Parameters for deleting table of contents entry
@@ -519,7 +509,7 @@ export class ApiHubAPI {
519
509
  owner: params.owner,
520
510
  apiName: params.apiName,
521
511
  version: "1.0.0",
522
- url: `https://app.swaggerhub.com/apis/${params.owner}/${params.apiName}/1.0.0`,
512
+ url: `${this.config.uiBasePath}/apis/${params.owner}/${params.apiName}/1.0.0`,
523
513
  operation,
524
514
  };
525
515
  }
@@ -551,7 +541,48 @@ export class ApiHubAPI {
551
541
  owner: params.owner,
552
542
  apiName: params.apiName,
553
543
  template: params.template,
554
- url: `https://app.swaggerhub.com/apis/${params.owner}/${params.apiName}`,
544
+ url: `${this.config.uiBasePath}/apis/${params.owner}/${params.apiName}`,
545
+ operation,
546
+ };
547
+ }
548
+ /**
549
+ * Create API from Prompt using SmartBear AI
550
+ * @param params Parameters for creating API from prompt including owner, api name, prompt, and specification type
551
+ * @returns Created API metadata with URL. HTTP 201 indicates creation, HTTP 200 for update, HTTP 205 for reload
552
+ */
553
+ async createApiFromPrompt(params) {
554
+ // Construct the URL with query parameters
555
+ const searchParams = new URLSearchParams();
556
+ const specType = params.specType ?? "openapi30x";
557
+ searchParams.append("specType", specType);
558
+ const url = `${this.config.registryBasePath}/apis/${encodeURIComponent(params.owner)}/${encodeURIComponent(params.apiName)}/.ai?${searchParams.toString()}`;
559
+ // Use POST method with JSON body containing the prompt
560
+ const response = await fetch(url, {
561
+ method: "POST",
562
+ headers: {
563
+ ...this.headers,
564
+ "Content-Type": "application/json",
565
+ },
566
+ body: JSON.stringify(params.prompt),
567
+ });
568
+ if (!response.ok) {
569
+ const errorText = await response.text().catch(() => "");
570
+ throw new ToolError(`SwaggerHub Registry API createApiFromPrompt failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}. URL: ${url}`);
571
+ }
572
+ // Determine operation type based on HTTP status code
573
+ // 201 = new API created, 200 = existing API updated, 205 = API saved and should be reloaded
574
+ const operation = response.status === 201 ? "create" : "update";
575
+ // Extract version from X-Version header
576
+ const version = response.headers.get("X-Version");
577
+ // Return formatted response with the required fields
578
+ return {
579
+ owner: params.owner,
580
+ apiName: params.apiName,
581
+ specType: specType,
582
+ version: version || undefined,
583
+ url: version
584
+ ? `${this.config.uiBasePath}/apis/${params.owner}/${params.apiName}/${version}`
585
+ : `${this.config.uiBasePath}/apis/${params.owner}/${params.apiName}`,
555
586
  operation,
556
587
  };
557
588
  }
@@ -595,7 +626,7 @@ export class ApiHubAPI {
595
626
  requestBody = JSON.stringify(parsedDefinition);
596
627
  }
597
628
  catch (error) {
598
- throw new Error(`Invalid JSON format in definition: ${error instanceof Error ? error.message : "Unknown error"}`);
629
+ throw new ToolError(`Invalid JSON format in definition: ${error instanceof Error ? error.message : "Unknown error"}`);
599
630
  }
600
631
  }
601
632
  const url = `${this.config.registryBasePath}/standardization/${encodeURIComponent(params.orgName)}/scan`;
@@ -609,8 +640,34 @@ export class ApiHubAPI {
609
640
  });
610
641
  if (!response.ok) {
611
642
  const errorText = await response.text().catch(() => "");
612
- throw new Error(`SwaggerHub Registry API scanStandardization failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}. URL: ${url}`);
643
+ throw new ToolError(`SwaggerHub Registry API scanStandardization failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}. URL: ${url}`);
613
644
  }
614
645
  return await this.handleResponse(response);
615
646
  }
647
+ /**
648
+ * Standardize and fix an API definition using AI
649
+ * @param params Parameters including owner, API name, and version
650
+ * @returns Standardization response with status and fixed definition
651
+ */
652
+ async standardizeApi(params) {
653
+ const url = `${this.config.registryBasePath}/apis/${encodeURIComponent(params.owner)}/${encodeURIComponent(params.api)}/${encodeURIComponent(params.version)}/standardize`;
654
+ const response = await fetch(url, {
655
+ method: "POST",
656
+ headers: this.headers,
657
+ });
658
+ if (!response.ok) {
659
+ const errorText = await response.text().catch(() => "");
660
+ throw new ToolError(`SwaggerHub Registry API standardizeApi failed - status: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}. URL: ${url}`);
661
+ }
662
+ const result = await this.handleResponse(response);
663
+ // Validate that we have the expected response structure
664
+ if (!hasMessage(result)) {
665
+ throw new ToolError("Unexpected response format from standardizeApi endpoint");
666
+ }
667
+ // If errorsFound is not present, default to 0 (no errors found)
668
+ if (!hasErrorsFound(result)) {
669
+ return { ...result, errorsFound: 0 };
670
+ }
671
+ return result;
672
+ }
616
673
  }
@@ -1,7 +1,8 @@
1
- export class ApiHubConfiguration {
1
+ export class SwaggerConfiguration {
2
2
  token;
3
3
  portalBasePath;
4
4
  registryBasePath;
5
+ uiBasePath;
5
6
  userManagementBasePath;
6
7
  headers;
7
8
  constructor(param) {
@@ -9,7 +10,8 @@ export class ApiHubConfiguration {
9
10
  this.portalBasePath =
10
11
  param.portalBasePath || "https://api.portal.swaggerhub.com/v1";
11
12
  this.registryBasePath =
12
- param.registryBasePath || "https://api.swaggerhub.com";
13
+ param.registryBasePath || "https://api.swaggerhub.com"; // Default for registry API
14
+ this.uiBasePath = param.uiBasePath || "https://app.swaggerhub.com"; // Default for UI
13
15
  this.userManagementBasePath =
14
16
  param.userManagementBasePath ||
15
17
  "https://api.swaggerhub.com/user-management/v1";
@@ -1,5 +1,5 @@
1
- export { ApiHubAPI } from "./api.js";
2
- export { ApiHubConfiguration, } from "./configuration.js";
1
+ export { SwaggerAPI } from "./api.js";
2
+ export { SwaggerConfiguration, } from "./configuration.js";
3
3
  export * from "./portal-types.js";
4
4
  export * from "./registry-types.js";
5
5
  export { TOOLS } from "./tools.js";