@nbakka/mcp-appium 2.0.95 → 2.0.97

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 +76 -36
  2. package/package.json +1 -1
package/lib/server.js CHANGED
@@ -828,16 +828,47 @@ tool(
828
828
 
829
829
  const parsedTestCases = parseTestCases(testCases);
830
830
 
831
+ // Initialize session with proper locking mechanism
831
832
  approvalSessions.set(sessionId, {
832
833
  status: 'pending',
833
834
  testCases: parsedTestCases,
834
835
  originalTestCases: JSON.parse(JSON.stringify(parsedTestCases)),
835
836
  server: null,
836
- startTime: Date.now()
837
+ startTime: Date.now(),
838
+ locked: false
837
839
  });
838
840
 
839
841
  const app = express();
840
- const port = 3001;
842
+
843
+ // Try multiple ports if 3001 is busy
844
+ let port = 3001;
845
+ let server = null;
846
+
847
+ const tryStartServer = (portToTry) => {
848
+ return new Promise((resolve, reject) => {
849
+ const testServer = app.listen(portToTry, () => {
850
+ resolve({ server: testServer, port: portToTry });
851
+ }).on('error', (err) => {
852
+ if (err.code === 'EADDRINUSE') {
853
+ reject(err);
854
+ } else {
855
+ reject(err);
856
+ }
857
+ });
858
+ });
859
+ };
860
+
861
+ // Try ports 3001-3010
862
+ for (let i = 0; i < 10; i++) {
863
+ try {
864
+ const result = await tryStartServer(port + i);
865
+ server = result.server;
866
+ port = result.port;
867
+ break;
868
+ } catch (err) {
869
+ if (i === 9) throw new Error(`No available ports found (tried ${port}-${port + 9})`);
870
+ }
871
+ }
841
872
 
842
873
  app.use(express.json());
843
874
  app.use(express.static(path.join(__dirname, 'review-ui')));
@@ -871,13 +902,16 @@ tool(
871
902
 
872
903
  app.post(`/approve/${sessionId}`, (req, res) => {
873
904
  const session = approvalSessions.get(sessionId);
874
- if (session) {
875
- // overwrite authoritative copy with the submitted version
876
- session.testCases = req.body;
877
- session.finalTestCases = req.body;
878
- session.status = 'approved';
879
- approvalSessions.set(sessionId, session);
880
- }
905
+ if (!session) return res.status(404).json({ error: 'Session not found' });
906
+
907
+ // Use atomic update with locking
908
+ session.locked = true;
909
+ session.testCases = req.body;
910
+ session.finalTestCases = req.body;
911
+ session.status = 'approved';
912
+ session.approvedAt = Date.now();
913
+ approvalSessions.set(sessionId, session);
914
+
881
915
  res.json({ status: 'approved', message: 'Test cases approved successfully!' });
882
916
  });
883
917
 
@@ -885,23 +919,24 @@ tool(
885
919
  const session = approvalSessions.get(sessionId);
886
920
  if (session) {
887
921
  session.status = 'cancelled';
922
+ session.cancelledAt = Date.now();
888
923
  approvalSessions.set(sessionId, session);
889
924
  }
890
925
  res.json({ status: 'cancelled', message: 'Review cancelled' });
891
926
  });
892
927
 
893
- const serverInstance = app.listen(port, async () => {
894
- try {
895
- const { default: open } = await import('open');
896
- await open(`http://localhost:${port}`);
897
- } catch { /* silent */ }
898
- });
899
-
928
+ // Update session with server reference
900
929
  const stored = approvalSessions.get(sessionId);
901
- stored.server = serverInstance;
930
+ stored.server = server;
902
931
  approvalSessions.set(sessionId, stored);
903
932
 
904
- // timeout safeguard
933
+ // Open browser
934
+ try {
935
+ const { default: open } = await import('open');
936
+ await open(`http://localhost:${port}`);
937
+ } catch { /* silent */ }
938
+
939
+ // Timeout safeguard - but don't override approved status
905
940
  setTimeout(() => {
906
941
  const s = approvalSessions.get(sessionId);
907
942
  if (s && s.status === 'pending') {
@@ -911,24 +946,23 @@ tool(
911
946
  }
912
947
  }, 300000);
913
948
 
914
- return {
915
- status: "review_started",
916
- sessionId,
917
- message: "Test case review interface opened.",
918
- testCasesCount: testCases.length,
919
- browserUrl: `http://localhost:${port}`,
920
- instructions: "Use check_approval_status tool to poll."
921
- };
949
+ return `✅ Test case review interface opened successfully!
950
+ Session ID: ${sessionId}
951
+ Test Cases Count: ${testCases.length}
952
+ Browser URL: http://localhost:${port}
953
+ Status: review_started
954
+
955
+ Instructions: Use check_approval_status tool with session ID "${sessionId}" to poll for approval status.`;
956
+
922
957
  } catch (err) {
923
- return { status: "error", message: `Error setting up review interface: ${err.message}` };
958
+ return `❌ Error setting up review interface: ${err.message}`;
924
959
  }
925
960
  }
926
961
  );
927
962
 
928
-
929
963
  tool(
930
964
  "check_approval_status",
931
- "Check the approval status of test cases review session (short-circuits if already approved)",
965
+ "Check the approval status of test cases review session (waits 20 seconds before checking)",
932
966
  { sessionId: zod_1.z.string().describe("Session ID from review_testcases") },
933
967
  async ({ sessionId }) => {
934
968
  const session = approvalSessions.get(sessionId);
@@ -937,6 +971,7 @@ tool(
937
971
  const now = Date.now();
938
972
  const elapsed = Math.floor((now - session.startTime) / 1000);
939
973
 
974
+ // Check if already approved (short-circuit without waiting)
940
975
  if (session.status === 'approved') {
941
976
  const approvedTestCases = session.finalTestCases || session.testCases;
942
977
  session.server?.close();
@@ -944,31 +979,36 @@ tool(
944
979
  return `✅ Test cases approved. Elapsed: ${elapsed}s\n${JSON.stringify(approvedTestCases, null, 2)}`;
945
980
  }
946
981
 
947
- await new Promise(r => setTimeout(r, 5000));
982
+ // Wait 20 seconds before checking status
983
+ await new Promise(r => setTimeout(r, 20000));
948
984
 
985
+ // Re-fetch session after wait
949
986
  const refreshed = approvalSessions.get(sessionId);
950
- if (!refreshed) return `❌ Session expired`;
987
+ if (!refreshed) return `❌ Session expired during wait`;
988
+
989
+ const newElapsed = Math.floor((Date.now() - refreshed.startTime) / 1000);
951
990
 
952
991
  if (refreshed.status === 'approved') {
953
992
  const approvedTestCases = refreshed.finalTestCases || refreshed.testCases;
954
993
  refreshed.server?.close();
955
994
  approvalSessions.delete(sessionId);
956
- return `✅ Test cases approved. Elapsed: ${elapsed + 5}s\n${JSON.stringify(approvedTestCases, null, 2)}`;
995
+ return `✅ Test cases approved. Elapsed: ${newElapsed}s\n${JSON.stringify(approvedTestCases, null, 2)}`;
957
996
  } else if (refreshed.status === 'cancelled') {
958
997
  refreshed.server?.close();
959
998
  approvalSessions.delete(sessionId);
960
- return `❌ Review cancelled. Elapsed: ${elapsed + 5}s`;
961
- } else if (refreshed.status === 'timeout' || elapsed > 300) {
999
+ return `❌ Review cancelled. Elapsed: ${newElapsed}s`;
1000
+ } else if (refreshed.status === 'timeout' || newElapsed > 300) {
962
1001
  refreshed.server?.close();
963
1002
  approvalSessions.delete(sessionId);
964
- return `⏰ Review session timed out. Elapsed: ${elapsed + 5}s`;
1003
+ return `⏰ Review session timed out. Elapsed: ${newElapsed}s`;
965
1004
  }
966
1005
 
967
- return `⏳ Still pending. Elapsed: ${elapsed + 5}s Remaining: ${Math.max(0, 300 - (elapsed + 5))}s`;
1006
+ return `⏳ Still pending. Elapsed: ${newElapsed}s Remaining: ${Math.max(0, 300 - newElapsed)}s`;
968
1007
  }
969
1008
  );
970
1009
 
971
1010
 
1011
+
972
1012
  return server;
973
1013
  };
974
1014
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nbakka/mcp-appium",
3
- "version": "2.0.95",
3
+ "version": "2.0.97",
4
4
  "description": "Appium MCP",
5
5
  "engines": {
6
6
  "node": ">=18"