@nbakka/mcp-appium 2.0.68 → 2.0.69

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 (2) hide show
  1. package/lib/server.js +241 -167
  2. package/package.json +1 -1
package/lib/server.js CHANGED
@@ -651,6 +651,9 @@ tool(
651
651
  }
652
652
  );
653
653
 
654
+ // Global state for tracking approval sessions
655
+ let approvalSessions = new Map();
656
+
654
657
  tool(
655
658
  "review_testcases",
656
659
  "Open JSON test cases in browser for manual approval",
@@ -664,193 +667,264 @@ tool(
664
667
  async ({ testCases }) => {
665
668
  try {
666
669
  const express = require('express');
670
+ const sessionId = Date.now().toString();
671
+
672
+ // Initialize session state
673
+ approvalSessions.set(sessionId, {
674
+ status: 'pending',
675
+ testCases: testCases,
676
+ server: null,
677
+ startTime: Date.now()
678
+ });
667
679
 
668
- return new Promise(async (resolve, reject) => {
669
- const app = express();
670
- const port = 3001;
671
- let server;
672
- let userApproved = false;
673
-
674
- // Serve a simple HTML page with test cases
675
- app.get("/", (req, res) => {
676
- let html = `
677
- <html>
678
- <head>
679
- <title>Test Case Approval</title>
680
- <style>
681
- body {
682
- font-family: Arial, sans-serif;
683
- margin: 20px;
684
- background-color: #f5f5f5;
685
- }
686
- .container {
687
- max-width: 1200px;
688
- margin: 0 auto;
689
- background: white;
690
- padding: 20px;
691
- border-radius: 8px;
692
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
693
- }
694
- h1 {
695
- color: #333;
696
- text-align: center;
697
- border-bottom: 2px solid #007cba;
698
- padding-bottom: 10px;
699
- }
700
- .test-case {
701
- margin: 15px 0;
702
- padding: 15px;
703
- border: 1px solid #ddd;
704
- border-radius: 8px;
705
- background: #fafafa;
706
- }
707
- .test-id {
708
- font-weight: bold;
709
- color: #007cba;
710
- font-size: 16px;
711
- }
712
- .test-title {
713
- font-weight: bold;
714
- margin: 5px 0;
715
- color: #333;
716
- }
717
- .test-description {
718
- color: #666;
719
- line-height: 1.4;
720
- }
721
- .button-container {
722
- text-align: center;
723
- margin-top: 30px;
724
- padding-top: 20px;
725
- border-top: 1px solid #ddd;
726
- }
727
- button {
728
- padding: 15px 30px;
729
- background: #28a745;
730
- color: white;
731
- border: none;
732
- border-radius: 5px;
733
- cursor: pointer;
734
- font-size: 16px;
735
- font-weight: bold;
736
- }
737
- button:hover {
738
- background: #218838;
739
- }
740
- .status {
741
- text-align: center;
742
- margin-top: 20px;
743
- font-weight: bold;
744
- display: none;
745
- }
746
- </style>
747
- </head>
748
- <body>
749
- <div class="container">
750
- <h1>Review Test Cases</h1>
751
- <div id="test-cases">
752
- `;
753
-
754
- testCases.forEach(tc => {
755
- html += `
756
- <div class="test-case">
757
- <div class="test-id">${tc.id}</div>
758
- <div class="test-title">${tc.title}</div>
759
- <div class="test-description">${tc.description}</div>
760
- </div>
761
- `;
762
- });
763
-
680
+ const app = express();
681
+ const port = 3001;
682
+
683
+ // Serve a simple HTML page with test cases
684
+ app.get("/", (req, res) => {
685
+ let html = `
686
+ <html>
687
+ <head>
688
+ <title>Test Case Approval</title>
689
+ <style>
690
+ body {
691
+ font-family: Arial, sans-serif;
692
+ margin: 20px;
693
+ background-color: #f5f5f5;
694
+ }
695
+ .container {
696
+ max-width: 1200px;
697
+ margin: 0 auto;
698
+ background: white;
699
+ padding: 20px;
700
+ border-radius: 8px;
701
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
702
+ }
703
+ h1 {
704
+ color: #333;
705
+ text-align: center;
706
+ border-bottom: 2px solid #007cba;
707
+ padding-bottom: 10px;
708
+ }
709
+ .test-case {
710
+ margin: 15px 0;
711
+ padding: 15px;
712
+ border: 1px solid #ddd;
713
+ border-radius: 8px;
714
+ background: #fafafa;
715
+ }
716
+ .test-id {
717
+ font-weight: bold;
718
+ color: #007cba;
719
+ font-size: 16px;
720
+ }
721
+ .test-title {
722
+ font-weight: bold;
723
+ margin: 5px 0;
724
+ color: #333;
725
+ }
726
+ .test-description {
727
+ color: #666;
728
+ line-height: 1.4;
729
+ }
730
+ .button-container {
731
+ text-align: center;
732
+ margin-top: 30px;
733
+ padding-top: 20px;
734
+ border-top: 1px solid #ddd;
735
+ }
736
+ button {
737
+ padding: 15px 30px;
738
+ background: #28a745;
739
+ color: white;
740
+ border: none;
741
+ border-radius: 5px;
742
+ cursor: pointer;
743
+ font-size: 16px;
744
+ font-weight: bold;
745
+ }
746
+ button:hover {
747
+ background: #218838;
748
+ }
749
+ .status {
750
+ text-align: center;
751
+ margin-top: 20px;
752
+ font-weight: bold;
753
+ display: none;
754
+ }
755
+ </style>
756
+ </head>
757
+ <body>
758
+ <div class="container">
759
+ <h1>Review Test Cases</h1>
760
+ <div id="test-cases">
761
+ `;
762
+
763
+ testCases.forEach(tc => {
764
764
  html += `
765
- </div>
766
- <div class="button-container">
767
- <button onclick="approveTestCases()">✓ Approve Test Cases</button>
768
- <div id="status" class="status"></div>
769
- </div>
765
+ <div class="test-case">
766
+ <div class="test-id">${tc.id}</div>
767
+ <div class="test-title">${tc.title}</div>
768
+ <div class="test-description">${tc.description}</div>
770
769
  </div>
771
-
772
- <script>
773
- function approveTestCases() {
774
- document.querySelector('button').disabled = true;
775
- document.querySelector('button').style.background = '#6c757d';
776
- document.querySelector('button').innerHTML = 'Processing...';
777
-
778
- const status = document.getElementById('status');
779
- status.style.display = 'block';
780
- status.style.color = '#28a745';
781
- status.innerHTML = 'Test cases approved! You can close this window.';
782
-
783
- fetch('/approve')
784
- .then(response => response.text())
785
- .then(data => {
786
- status.innerHTML = data + ' You can close this window.';
787
- setTimeout(() => {
788
- window.close();
789
- }, 2000);
790
- })
791
- .catch(error => {
792
- status.style.color = '#dc3545';
793
- status.innerHTML = 'Error: ' + error.message;
794
- });
795
- }
796
- </script>
797
- </body>
798
- </html>
799
770
  `;
800
- res.send(html);
801
771
  });
802
772
 
803
- // Approval endpoint
804
- app.get("/approve", (req, res) => {
805
- userApproved = true;
806
- res.send(" Test cases approved successfully!");
807
-
808
- // Close server after a short delay
809
- setTimeout(() => {
810
- if (server) {
811
- server.close();
773
+ html += `
774
+ </div>
775
+ <div class="button-container">
776
+ <button onclick="approveTestCases()">✓ Approve Test Cases</button>
777
+ <div id="status" class="status"></div>
778
+ </div>
779
+ </div>
780
+
781
+ <script>
782
+ function approveTestCases() {
783
+ document.querySelector('button').disabled = true;
784
+ document.querySelector('button').style.background = '#6c757d';
785
+ document.querySelector('button').innerHTML = 'Processing...';
786
+
787
+ const status = document.getElementById('status');
788
+ status.style.display = 'block';
789
+ status.style.color = '#28a745';
790
+ status.innerHTML = 'Test cases approved! You can close this window.';
791
+
792
+ fetch('/approve/${sessionId}')
793
+ .then(response => response.text())
794
+ .then(data => {
795
+ status.innerHTML = data + ' You can close this window.';
796
+ setTimeout(() => {
797
+ window.close();
798
+ }, 2000);
799
+ })
800
+ .catch(error => {
801
+ status.style.color = '#dc3545';
802
+ status.innerHTML = 'Error: ' + error.message;
803
+ });
812
804
  }
813
- resolve("✓ Test cases have been approved by the user. Continuing with next operations...");
814
- }, 1000);
815
- });
805
+ </script>
806
+ </body>
807
+ </html>
808
+ `;
809
+ res.send(html);
810
+ });
816
811
 
817
- // Start server
818
- server = app.listen(port, async () => {
819
- console.log(`Test case review server started on http://localhost:${port}`);
820
-
821
- try {
822
- // Use dynamic import for the open package (ES module)
823
- const { default: open } = await import('open');
824
- await open(`http://localhost:${port}`);
825
- console.log('Browser opened successfully');
826
- } catch (openError) {
827
- console.log('Failed to open browser automatically:', openError.message);
828
- console.log(`Please manually open: http://localhost:${port}`);
829
- }
830
- });
812
+ // Approval endpoint
813
+ app.get(`/approve/${sessionId}`, (req, res) => {
814
+ const session = approvalSessions.get(sessionId);
815
+ if (session) {
816
+ session.status = 'approved';
817
+ approvalSessions.set(sessionId, session);
818
+ }
819
+ res.send("✓ Test cases approved successfully!");
820
+ });
831
821
 
832
- // Handle server errors
833
- server.on('error', (err) => {
834
- reject(`Server error: ${err.message}`);
835
- });
822
+ // Start server
823
+ const server = app.listen(port, async () => {
824
+ console.log(`Test case review server started on http://localhost:${port}`);
825
+
826
+ try {
827
+ // Use dynamic import for the open package (ES module)
828
+ const { default: open } = await import('open');
829
+ await open(`http://localhost:${port}`);
830
+ console.log('Browser opened successfully');
831
+ } catch (openError) {
832
+ console.log('Failed to open browser automatically:', openError.message);
833
+ console.log(`Please manually open: http://localhost:${port}`);
834
+ }
835
+ });
836
836
 
837
- // Timeout after 10 minutes if user doesn't approve
838
- setTimeout(() => {
839
- if (!userApproved && server) {
840
- server.close();
841
- reject("⚠️ Review session timed out after 10 minutes. Test cases were not approved.");
837
+ // Store server reference in session
838
+ const session = approvalSessions.get(sessionId);
839
+ session.server = server;
840
+ approvalSessions.set(sessionId, session);
841
+
842
+ // Auto cleanup after 5 minutes
843
+ setTimeout(() => {
844
+ const session = approvalSessions.get(sessionId);
845
+ if (session && session.status === 'pending') {
846
+ session.status = 'timeout';
847
+ if (session.server) {
848
+ session.server.close();
842
849
  }
843
- }, 600000); // 10 minutes
850
+ approvalSessions.set(sessionId, session);
851
+ }
852
+ }, 300000); // 5 minutes
853
+
854
+ return JSON.stringify({
855
+ status: "review_started",
856
+ sessionId: sessionId,
857
+ message: "Test case review interface opened in browser. Use check_approval_status tool to poll for approval.",
858
+ testCasesCount: testCases.length,
859
+ browserUrl: `http://localhost:${port}`,
860
+ instructions: "Poll every 10 seconds using check_approval_status tool until approved or timeout (5 minutes)"
844
861
  });
845
862
 
846
863
  } catch (err) {
847
- return `❌ Error setting up review interface: ${err.message}`;
864
+ return JSON.stringify({
865
+ status: "error",
866
+ message: `Error setting up review interface: ${err.message}`
867
+ });
848
868
  }
849
869
  }
850
870
  );
851
871
 
872
+ tool(
873
+ "check_approval_status",
874
+ "Check the approval status of test cases review session",
875
+ {
876
+ sessionId: zod_1.z.string().describe("The session ID returned from review_testcases tool")
877
+ },
878
+ async ({ sessionId }) => {
879
+ const session = approvalSessions.get(sessionId);
880
+
881
+ if (!session) {
882
+ return JSON.stringify({
883
+ status: "error",
884
+ message: "Session not found. Invalid session ID."
885
+ });
886
+ }
887
+
888
+ const currentTime = Date.now();
889
+ const elapsedTime = Math.floor((currentTime - session.startTime) / 1000);
852
890
 
891
+ if (session.status === 'approved') {
892
+ // Clean up session and close server
893
+ if (session.server) {
894
+ session.server.close();
895
+ }
896
+ approvalSessions.delete(sessionId);
897
+
898
+ return JSON.stringify({
899
+ status: "approved",
900
+ message: "Test cases have been approved by the user. Continuing with next operations...",
901
+ testCasesCount: session.testCases.length,
902
+ elapsedTime: elapsedTime
903
+ });
904
+ } else if (session.status === 'timeout' || elapsedTime > 300) { // 5 minutes
905
+ // Clean up session and close server
906
+ if (session.server) {
907
+ session.server.close();
908
+ }
909
+ approvalSessions.delete(sessionId);
853
910
 
911
+ return JSON.stringify({
912
+ status: "timeout",
913
+ message: "Review session timed out after 5 minutes. Test cases were not approved.",
914
+ testCasesCount: session.testCases.length,
915
+ elapsedTime: elapsedTime
916
+ });
917
+ } else {
918
+ return JSON.stringify({
919
+ status: "pending",
920
+ message: "Test cases are still pending approval. Continue polling.",
921
+ testCasesCount: session.testCases.length,
922
+ elapsedTime: elapsedTime,
923
+ remainingTime: Math.max(0, 300 - elapsedTime)
924
+ });
925
+ }
926
+ }
927
+ );
854
928
  return server;
855
929
  };
856
930
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nbakka/mcp-appium",
3
- "version": "2.0.68",
3
+ "version": "2.0.69",
4
4
  "description": "Appium MCP",
5
5
  "engines": {
6
6
  "node": ">=18"