@smartbear/mcp 0.13.5 → 0.14.1

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 (27) hide show
  1. package/dist/bugsnag/client.js +169 -10
  2. package/dist/package.json.js +1 -1
  3. package/dist/pactflow/client/utils.js +10 -4
  4. package/dist/swagger/client/portal-types.js +7 -1
  5. package/dist/swagger/client/tools.js +1 -1
  6. package/dist/zephyr/client.js +13 -1
  7. package/dist/zephyr/common/api-client.js +35 -1
  8. package/dist/zephyr/common/rest-api-schemas.js +1913 -1755
  9. package/dist/zephyr/common/utils.js +20 -0
  10. package/dist/zephyr/tool/environment/get-environments.js +4 -4
  11. package/dist/zephyr/tool/priority/get-priorities.js +4 -4
  12. package/dist/zephyr/tool/project/get-project.js +4 -4
  13. package/dist/zephyr/tool/project/get-projects.js +4 -4
  14. package/dist/zephyr/tool/status/get-statuses.js +4 -4
  15. package/dist/zephyr/tool/test-case/create-test-case.js +69 -0
  16. package/dist/zephyr/tool/test-case/create-web-link.js +46 -0
  17. package/dist/zephyr/tool/test-case/get-test-case.js +4 -4
  18. package/dist/zephyr/tool/test-case/get-test-cases.js +4 -4
  19. package/dist/zephyr/tool/test-case/update-test-case.js +79 -0
  20. package/dist/zephyr/tool/test-cycle/create-test-cycle.js +70 -0
  21. package/dist/zephyr/tool/test-cycle/get-test-cycle.js +4 -4
  22. package/dist/zephyr/tool/test-cycle/get-test-cycles.js +4 -4
  23. package/dist/zephyr/tool/test-cycle/update-test-cycle.js +90 -0
  24. package/dist/zephyr/tool/test-execution/create-test-execution.js +66 -0
  25. package/dist/zephyr/tool/test-execution/get-test-execution.js +4 -4
  26. package/dist/zephyr/tool/test-execution/get-test-executions.js +4 -4
  27. package/package.json +1 -1
@@ -28,7 +28,16 @@ const PERMITTED_UPDATE_OPERATIONS = [
28
28
  "fix",
29
29
  "ignore",
30
30
  "discard",
31
- "undiscard"
31
+ "undiscard",
32
+ "snooze",
33
+ "link_issue",
34
+ "unlink_issue"
35
+ ];
36
+ const PERMITTED_REOPEN_CONDITIONS = [
37
+ "occurs_after",
38
+ "n_occurrences_in_m_hours",
39
+ "n_additional_occurrences",
40
+ "n_additional_users"
32
41
  ];
33
42
  const ConfigurationSchema = z.object({
34
43
  auth_token: z.string().describe("BugSnag personal authentication token"),
@@ -656,7 +665,30 @@ class BugsnagClient {
656
665
  const updateErrorInputSchema = z.object({
657
666
  projectId: toolInputParameters.projectId,
658
667
  errorId: toolInputParameters.errorId,
659
- operation: z.enum(PERMITTED_UPDATE_OPERATIONS).describe("The operation to apply to the error")
668
+ operation: z.enum(PERMITTED_UPDATE_OPERATIONS).describe("The operation to apply to the error"),
669
+ issue_url: z.string().optional().describe(
670
+ "The URL of the issue to link to the error - required when operation is 'link_issue'"
671
+ ),
672
+ reopenRules: z.object({
673
+ reopenIf: z.enum(PERMITTED_REOPEN_CONDITIONS).describe("Condition for when the error should be reopened"),
674
+ additionalUsers: z.number().min(1).max(1e5).optional().describe(
675
+ "for n_additional_users reopen rules, the number of additional users to be affected by an Error before the Error is automatically reopened."
676
+ ),
677
+ seconds: z.number().min(1).optional().describe(
678
+ "for occurs_after reopen rules, the number of seconds that the Error should be snoozed for."
679
+ ),
680
+ occurrences: z.number().min(1).optional().describe(
681
+ "for n_occurrences_in_m_hours reopen rules, the number of occurrences to allow in the number of hours indicated by the hours field, before the Error is automatically reopened."
682
+ ),
683
+ hours: z.number().min(1).optional().describe(
684
+ "for n_occurrences_in_m_hours reopen rules, the number of hours."
685
+ ),
686
+ additionalOccurrences: z.number().min(1).optional().describe(
687
+ "or n_additional_occurrences reopen rules, the number of additional occurrences allowed before reopening."
688
+ )
689
+ }).optional().describe(
690
+ "Reopen rules for snooze operation - required when operation is 'snooze'"
691
+ )
660
692
  });
661
693
  register(
662
694
  {
@@ -666,7 +698,8 @@ class BugsnagClient {
666
698
  useCases: [
667
699
  "Mark an error as open, fixed or ignored",
668
700
  "Discard or un-discard an error",
669
- "Update the severity of an error"
701
+ "Update the severity of an error",
702
+ "Snooze an error with defined conditions for when it should be reopened"
670
703
  ],
671
704
  inputSchema: updateErrorInputSchema,
672
705
  examples: [
@@ -677,10 +710,72 @@ class BugsnagClient {
677
710
  operation: "fix"
678
711
  },
679
712
  expectedOutput: "Success response indicating the error was marked as fixed"
713
+ },
714
+ {
715
+ description: "Snooze an error for 1 hour",
716
+ parameters: {
717
+ errorId: "6863e2af8c857c0a5023b411",
718
+ operation: "snooze",
719
+ reopenRules: {
720
+ reopenIf: "occurs_after",
721
+ seconds: 3600
722
+ }
723
+ },
724
+ expectedOutput: "Success response indicating the error was snoozed for 1 hour"
725
+ },
726
+ {
727
+ description: "Snooze an error until 5 additional users are affected",
728
+ parameters: {
729
+ errorId: "6863e2af8c857c0a5023b411",
730
+ operation: "snooze",
731
+ reopenRules: {
732
+ reopenIf: "n_additional_users",
733
+ additionalUsers: 5
734
+ }
735
+ },
736
+ expectedOutput: "Success response indicating the error was snoozed until 5 additional users are affected"
737
+ },
738
+ {
739
+ description: "Snooze an error until 10 occurrences in 24 hours",
740
+ parameters: {
741
+ errorId: "6863e2af8c857c0a5023b411",
742
+ operation: "snooze",
743
+ reopenRules: {
744
+ reopenIf: "n_occurrences_in_m_hours",
745
+ occurrences: 10,
746
+ hours: 24
747
+ }
748
+ },
749
+ expectedOutput: "Success response indicating the error was snoozed until 10 occurrences in 24 hours"
750
+ },
751
+ {
752
+ description: "Link a Jira issue to an error",
753
+ parameters: {
754
+ errorId: "6863e2af8c857c0a5023b411",
755
+ operation: "link_issue",
756
+ issue_url: "https://smartbear.atlassian.net/browse/PIPE-9547"
757
+ },
758
+ expectedOutput: "Success response indicating the Jira issue was linked to the error"
759
+ },
760
+ {
761
+ description: "Unlink a Jira issue from an error",
762
+ parameters: {
763
+ errorId: "6863e2af8c857c0a5023b411",
764
+ operation: "unlink_issue"
765
+ },
766
+ expectedOutput: "Success response indicating the Jira issue was unlinked from the error"
680
767
  }
681
768
  ],
682
769
  hints: [
683
- "Only use valid operations - BugSnag may reject invalid values"
770
+ "Only use valid operations - BugSnag may reject invalid values",
771
+ "When using 'snooze' operation, reopenRules parameter is required",
772
+ "When using 'link_issue' operation, issue_url parameter is required",
773
+ "Use 'unlink_issue' to remove the link between an error and its issue",
774
+ "For 'occurs_after' reopen rules, specify 'seconds' parameter",
775
+ "For 'n_additional_users' reopen rules, specify 'additionalUsers' parameter (max 100,000)",
776
+ "For 'n_occurrences_in_m_hours' reopen rules, specify both 'occurrences' and 'hours' parameters",
777
+ "For 'n_additional_occurrences' reopen rules, specify 'additionalOccurrences' parameter",
778
+ "Snoozing temporarily silences an error until the specified reopen condition is met"
684
779
  ],
685
780
  readOnly: false,
686
781
  idempotent: false
@@ -688,6 +783,39 @@ class BugsnagClient {
688
783
  async (args, _extra) => {
689
784
  const params = updateErrorInputSchema.parse(args);
690
785
  const project = await this.getInputProject(params.projectId);
786
+ if (params.operation === "snooze" && !params.reopenRules) {
787
+ throw new ToolError(
788
+ "reopenRules parameter is required when using 'snooze' operation"
789
+ );
790
+ }
791
+ if (params.operation === "link_issue" && !params.issue_url) {
792
+ throw new ToolError(
793
+ "'issue_url' parameter is required for 'link_issue' operation"
794
+ );
795
+ }
796
+ if (params.reopenRules) {
797
+ const { reopenIf } = params.reopenRules;
798
+ if (reopenIf === "occurs_after" && !params.reopenRules.seconds) {
799
+ throw new ToolError(
800
+ "'seconds' parameter is required for 'occurs_after' reopen rules"
801
+ );
802
+ }
803
+ if (reopenIf === "n_additional_users" && !params.reopenRules.additionalUsers) {
804
+ throw new ToolError(
805
+ "'additionalUsers' parameter is required for 'n_additional_users' reopen rules"
806
+ );
807
+ }
808
+ if (reopenIf === "n_occurrences_in_m_hours" && (!params.reopenRules.occurrences || !params.reopenRules.hours)) {
809
+ throw new ToolError(
810
+ "Both 'occurrences' and 'hours' parameters are required for 'n_occurrences_in_m_hours' reopen rules"
811
+ );
812
+ }
813
+ if (reopenIf === "n_additional_occurrences" && !params.reopenRules.additionalOccurrences) {
814
+ throw new ToolError(
815
+ "'additionalOccurrences' parameter is required for 'n_additional_occurrences' reopen rules"
816
+ );
817
+ }
818
+ }
691
819
  let severity;
692
820
  if (params.operation === "override_severity") {
693
821
  const result2 = await getInput({
@@ -708,15 +836,46 @@ class BugsnagClient {
708
836
  severity = result2.content.severity;
709
837
  }
710
838
  }
839
+ let reopenRules;
840
+ if (params.reopenRules) {
841
+ reopenRules = {
842
+ reopen_if: params.reopenRules.reopenIf
843
+ };
844
+ if (params.reopenRules.additionalUsers !== void 0) {
845
+ reopenRules.additional_users = params.reopenRules.additionalUsers;
846
+ }
847
+ if (params.reopenRules.seconds !== void 0) {
848
+ reopenRules.seconds = params.reopenRules.seconds;
849
+ }
850
+ if (params.reopenRules.occurrences !== void 0) {
851
+ reopenRules.occurrences = params.reopenRules.occurrences;
852
+ }
853
+ if (params.reopenRules.hours !== void 0) {
854
+ reopenRules.hours = params.reopenRules.hours;
855
+ }
856
+ if (params.reopenRules.additionalOccurrences !== void 0) {
857
+ reopenRules.additional_occurrences = params.reopenRules.additionalOccurrences;
858
+ }
859
+ }
860
+ const errorUpdateRequestBody = {
861
+ operation: Object.values(ErrorUpdateRequest.OperationEnum).find(
862
+ (value) => value === params.operation
863
+ )
864
+ };
865
+ if (severity !== void 0) {
866
+ errorUpdateRequestBody.severity = severity;
867
+ }
868
+ if (reopenRules !== void 0) {
869
+ errorUpdateRequestBody.reopen_rules = reopenRules;
870
+ }
871
+ if (params.operation === "link_issue" && params.issue_url) {
872
+ errorUpdateRequestBody.issue_url = params.issue_url;
873
+ errorUpdateRequestBody.verify_issue_url = true;
874
+ }
711
875
  const result = await this.errorsApi.updateErrorOnProject(
712
876
  project.id,
713
877
  params.errorId,
714
- {
715
- operation: Object.values(ErrorUpdateRequest.OperationEnum).find(
716
- (value) => value === params.operation
717
- ),
718
- severity
719
- }
878
+ errorUpdateRequestBody
720
879
  );
721
880
  return {
722
881
  content: [
@@ -1,4 +1,4 @@
1
- const version = "0.13.5";
1
+ const version = "0.14.1";
2
2
  const config = { "mcpServerName": "SmartBear MCP Server" };
3
3
  const packageJson = {
4
4
  version,
@@ -43,12 +43,18 @@ async function getRemoteSpecContents(openAPISchema) {
43
43
  try {
44
44
  return yaml.load(specRawBody);
45
45
  } catch (yamlError) {
46
- throw new ToolError(
47
- `Unsupported Content-Type: ${remoteSpec.headers.get(
48
- "Content-Type"
49
- )} for remote OpenAPI document. Found following parse errors:-
46
+ const isStdioTransport = process.env.MCP_TRANSPORT?.toLowerCase() === "stdio";
47
+ if (isStdioTransport) {
48
+ throw new ToolError(
49
+ `Unsupported Content-Type: ${remoteSpec.headers.get(
50
+ "Content-Type"
51
+ )} for remote OpenAPI document. Found following parse errors:-
50
52
  JSON parse error: ${jsonError}
51
53
  YAML parse error: ${yamlError}`
54
+ );
55
+ }
56
+ throw new ToolError(
57
+ `Failed to parse remote OpenAPI document. Ensure the URL returns valid JSON or YAML content.`
52
58
  );
53
59
  }
54
60
  }
@@ -60,6 +60,9 @@ const CreateTableOfContentsArgsSchema = z.object({
60
60
  type: z.enum(["apiUrl", "html", "markdown"]).describe(
61
61
  "Content type - 'apiUrl' for API references, 'html' for HTML content, or 'markdown' for Markdown content"
62
62
  ),
63
+ source: z.enum(["internal", "external"]).optional().describe(
64
+ "Source of the document content - 'internal' allows to edit content in both UI and API, 'external' enables editing only via API."
65
+ ),
63
66
  url: z.string().optional().describe(
64
67
  "URL for API reference content (required when type is 'apiUrl')"
65
68
  ),
@@ -186,11 +189,14 @@ const GetDocumentArgsSchema = z.object({
186
189
  });
187
190
  const UpdateDocumentArgsSchema = z.object({
188
191
  documentId: z.string().describe("Document UUID - unique identifier for the document"),
189
- content: z.string().describe(
192
+ content: z.string().optional().describe(
190
193
  "The document content to update (HTML or Markdown based on document type)"
191
194
  ),
192
195
  type: z.enum(["html", "markdown"]).optional().describe(
193
196
  "Content type - 'html' for HTML content or 'markdown' for Markdown content"
197
+ ),
198
+ source: z.enum(["internal", "external"]).optional().describe(
199
+ "Source of the document content - 'internal' allows to edit content in both UI and API, 'external' enables editing only via API."
194
200
  )
195
201
  });
196
202
  const DeleteTableOfContentsArgsSchema = z.object({
@@ -96,7 +96,7 @@ const TOOLS = [
96
96
  },
97
97
  {
98
98
  title: "Update Document",
99
- summary: "Update the content of an existing document. Supports both HTML and Markdown content types.",
99
+ summary: "Update the content or source of an existing document. Supports both HTML and Markdown content types.",
100
100
  inputSchema: UpdateDocumentArgsSchema,
101
101
  handler: "updateDocument"
102
102
  },
@@ -5,10 +5,16 @@ import { GetPriorities } from "./tool/priority/get-priorities.js";
5
5
  import { GetProject } from "./tool/project/get-project.js";
6
6
  import { GetProjects } from "./tool/project/get-projects.js";
7
7
  import { GetStatuses } from "./tool/status/get-statuses.js";
8
+ import { CreateTestCase } from "./tool/test-case/create-test-case.js";
9
+ import { CreateTestCaseWebLink } from "./tool/test-case/create-web-link.js";
8
10
  import { GetTestCase } from "./tool/test-case/get-test-case.js";
9
11
  import { GetTestCases } from "./tool/test-case/get-test-cases.js";
12
+ import { UpdateTestCase } from "./tool/test-case/update-test-case.js";
13
+ import { CreateTestCycle } from "./tool/test-cycle/create-test-cycle.js";
10
14
  import { GetTestCycle } from "./tool/test-cycle/get-test-cycle.js";
11
15
  import { GetTestCycles } from "./tool/test-cycle/get-test-cycles.js";
16
+ import { UpdateTestCycle } from "./tool/test-cycle/update-test-cycle.js";
17
+ import { CreateTestExecution } from "./tool/test-execution/create-test-execution.js";
12
18
  import { GetTestExecution } from "./tool/test-execution/get-test-execution.js";
13
19
  import { GetTestExecutions } from "./tool/test-execution/get-test-executions.js";
14
20
  const BASE_URL_DEFAULT = "https://api.zephyrscale.smartbear.com/v2";
@@ -47,7 +53,13 @@ class ZephyrClient {
47
53
  new GetEnvironments(this),
48
54
  new GetTestCase(this),
49
55
  new GetTestExecution(this),
50
- new GetTestExecutions(this)
56
+ new GetTestExecutions(this),
57
+ new CreateTestCase(this),
58
+ new CreateTestCycle(this),
59
+ new UpdateTestCase(this),
60
+ new UpdateTestCycle(this),
61
+ new CreateTestExecution(this),
62
+ new CreateTestCaseWebLink(this)
51
63
  ];
52
64
  for (const tool of tools) {
53
65
  register(tool.specification, tool.handle);
@@ -25,6 +25,28 @@ class ApiClient {
25
25
  });
26
26
  return await this.validateAndGetResponseBody(response);
27
27
  }
28
+ async post(endpoint, body) {
29
+ const response = await fetch(this.getUrl(endpoint), {
30
+ method: "POST",
31
+ headers: {
32
+ ...this.defaultHeaders,
33
+ "Content-Type": "application/json"
34
+ },
35
+ body: JSON.stringify(body)
36
+ });
37
+ return await this.validateAndGetResponseBody(response);
38
+ }
39
+ async put(endpoint, body) {
40
+ const response = await fetch(this.getUrl(endpoint), {
41
+ method: "PUT",
42
+ headers: {
43
+ ...this.defaultHeaders,
44
+ "Content-Type": "application/json"
45
+ },
46
+ body: JSON.stringify(body)
47
+ });
48
+ return await this.validateAndGetResponseBody(response);
49
+ }
28
50
  async validateAndGetResponseBody(response) {
29
51
  if (!response.ok) {
30
52
  const errorText = await response.text();
@@ -32,7 +54,19 @@ class ApiClient {
32
54
  `Request failed with status ${response.status}: ${errorText}`
33
55
  );
34
56
  }
35
- return response.json();
57
+ const contentLength = response.headers.get("content-length");
58
+ if (response.status === 204 || contentLength === "0") {
59
+ return {};
60
+ }
61
+ const text = await response.text();
62
+ if (!text?.trim()) {
63
+ return {};
64
+ }
65
+ try {
66
+ return JSON.parse(text);
67
+ } catch {
68
+ return { data: text };
69
+ }
36
70
  }
37
71
  }
38
72
  export {