@nbakka/mcp-appium 2.0.67 → 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 +242 -62
  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,87 +667,264 @@ tool(
664
667
  async ({ testCases }) => {
665
668
  try {
666
669
  const express = require('express');
667
- const open = require('open');
668
-
669
- return new Promise((resolve, reject) => {
670
- const app = express();
671
- const port = 3001;
672
- let server;
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 { font-family: Arial, sans-serif; margin: 20px; }
682
- h1 { color: #333; }
683
- li { margin: 10px 0; padding: 10px; border: 1px solid #ddd; border-radius: 5px; }
684
- button { padding: 10px 20px; background: #007cba; color: white; border: none; border-radius: 5px; cursor: pointer; }
685
- button:hover { background: #005a87; }
686
- </style>
687
- </head>
688
- <body>
689
- <h1>Review Test Cases</h1>
690
- <ul>
691
- `;
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
+ });
692
679
 
693
- testCases.forEach(tc => {
694
- html += `<li><strong>${tc.id} - ${tc.title}</strong><p>${tc.description}</p></li>`;
695
- });
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
+ `;
696
762
 
763
+ testCases.forEach(tc => {
697
764
  html += `
698
- </ul>
699
- <button onclick="fetch('/approve').then(()=>window.close())">Approve</button>
700
- </body>
701
- </html>
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>
769
+ </div>
702
770
  `;
703
- res.send(html);
704
771
  });
705
772
 
706
- // Approval endpoint
707
- app.get("/approve", (req, res) => {
708
- res.send("Approved!");
709
- if (server) {
710
- server.close();
711
- }
712
- resolve("Test cases approved by user");
713
- });
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
+ });
804
+ }
805
+ </script>
806
+ </body>
807
+ </html>
808
+ `;
809
+ res.send(html);
810
+ });
714
811
 
715
- // Start server and open browser
716
- server = app.listen(port, async () => {
717
- try {
718
- await open(`http://localhost:${port}`);
719
- } catch (openError) {
720
- console.error('Failed to open browser:', openError);
721
- // Fallback: just provide the URL
722
- resolve(`Test cases review page available at: http://localhost:${port}`);
723
- }
724
- });
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
+ });
725
821
 
726
- // Handle server errors
727
- server.on('error', (err) => {
728
- reject(`Server error: ${err.message}`);
729
- });
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
+ });
730
836
 
731
- // Timeout after 5 minutes
732
- setTimeout(() => {
733
- if (server) {
734
- server.close();
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();
735
849
  }
736
- reject("Review session timed out after 5 minutes");
737
- }, 300000);
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)"
738
861
  });
739
862
 
740
863
  } catch (err) {
741
- 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
+ });
742
868
  }
743
869
  }
744
870
  );
745
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);
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);
746
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);
747
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
+ );
748
928
  return server;
749
929
  };
750
930
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nbakka/mcp-appium",
3
- "version": "2.0.67",
3
+ "version": "2.0.69",
4
4
  "description": "Appium MCP",
5
5
  "engines": {
6
6
  "node": ">=18"