Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { FastMCP } from 'fastmcp';
import registerTools from './tools/index.js';
import registerResources from './resources/index.js';
import { hasActiveSession, safeDeleteSession } from './tools/sessionStore.js';

const server = new FastMCP({
name: 'Jarvis Appium',
Expand All @@ -11,4 +12,28 @@

registerResources(server);
registerTools(server);

// Handle client connection and disconnection events
server.on("connect", (event) => {

Check failure on line 17 in src/server.ts

View workflow job for this annotation

GitHub Actions / ESLint, Prettier, and Tests

Replace `"connect",·(event)` with `'connect',·event`
console.log("Client connected:", event.session);

Check failure on line 18 in src/server.ts

View workflow job for this annotation

GitHub Actions / ESLint, Prettier, and Tests

Replace `"Client·connected:"` with `'Client·connected:'`
});

server.on("disconnect", async (event) => {

Check failure on line 21 in src/server.ts

View workflow job for this annotation

GitHub Actions / ESLint, Prettier, and Tests

Replace `"disconnect",·async·(event)` with `'disconnect',·async·event`
console.log("Client disconnected:", event.session);

Check failure on line 22 in src/server.ts

View workflow job for this annotation

GitHub Actions / ESLint, Prettier, and Tests

Replace `"Client·disconnected:"` with `'Client·disconnected:'`
// Only try to clean up if there's an active session
if (hasActiveSession()) {
try {
console.log("Active session detected on disconnect, cleaning up...");

Check failure on line 26 in src/server.ts

View workflow job for this annotation

GitHub Actions / ESLint, Prettier, and Tests

Replace `"Active·session·detected·on·disconnect,·cleaning·up..."` with `'Active·session·detected·on·disconnect,·cleaning·up...'`
const deleted = await safeDeleteSession();
if (deleted) {
console.log("Session cleaned up successfully on disconnect.");

Check failure on line 29 in src/server.ts

View workflow job for this annotation

GitHub Actions / ESLint, Prettier, and Tests

Replace `"Session·cleaned·up·successfully·on·disconnect."` with `'Session·cleaned·up·successfully·on·disconnect.'`
}
} catch (error) {
console.error("Error cleaning up session on disconnect:", error);

Check failure on line 32 in src/server.ts

View workflow job for this annotation

GitHub Actions / ESLint, Prettier, and Tests

Replace `"Error·cleaning·up·session·on·disconnect:"` with `'Error·cleaning·up·session·on·disconnect:'`
}
} else {
console.log("No active session to clean up on disconnect.");

Check failure on line 35 in src/server.ts

View workflow job for this annotation

GitHub Actions / ESLint, Prettier, and Tests

Replace `"No·active·session·to·clean·up·on·disconnect."` with `'No·active·session·to·clean·up·on·disconnect.'`
}
});

export default server;
9 changes: 7 additions & 2 deletions src/tools/create-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
*/
import { z } from 'zod';
import fs from 'fs';
import path from 'path';
import { AndroidUiautomator2Driver } from 'appium-uiautomator2-driver';
import { XCUITestDriver } from 'appium-xcuitest-driver';
import { setSession, getDriver, getSessionId } from './sessionStore.js';
import { setSession, hasActiveSession, safeDeleteSession } from './sessionStore.js';

Check failure on line 8 in src/tools/create-session.ts

View workflow job for this annotation

GitHub Actions / ESLint, Prettier, and Tests

Replace `·setSession,·hasActiveSession,·safeDeleteSession·` with `⏎··setSession,⏎··hasActiveSession,⏎··safeDeleteSession,⏎`

// Define capabilities type
interface Capabilities {
Expand Down Expand Up @@ -44,6 +43,12 @@
},
execute: async (args: any, context: any): Promise<any> => {
try {
// Check if there's an existing session and clean it up first
if (hasActiveSession()) {
console.log('Existing session detected, cleaning up before creating new session...');

Check failure on line 48 in src/tools/create-session.ts

View workflow job for this annotation

GitHub Actions / ESLint, Prettier, and Tests

Replace `'Existing·session·detected,·cleaning·up·before·creating·new·session...'` with `⏎············'Existing·session·detected,·cleaning·up·before·creating·new·session...'⏎··········`
await safeDeleteSession();
}

const { platform, capabilities: customCapabilities } = args;

let defaultCapabilities: Capabilities;
Expand Down
46 changes: 46 additions & 0 deletions src/tools/delete-session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Tool to delete the current mobile session and clean up resources
*/
import { z } from 'zod';
import { safeDeleteSession } from './sessionStore.js';

export default function deleteSession(server: any): void {
server.addTool({
name: 'delete_session',
description: 'Delete the current mobile session and clean up resources.',
parameters: z.object({}),
annotations: {
destructiveHint: true,
readOnlyHint: false,
openWorldHint: false,
},
execute: async (): Promise<any> => {
try {
const deleted = await safeDeleteSession();

if (deleted) {
return {
content: [
{
type: 'text',
text: 'Session deleted successfully.',
},
],
};
} else {
return {
content: [
{
type: 'text',
text: 'No active session found or deletion already in progress.',
},
],
};
}
} catch (error: any) {
console.error(`Error deleting session`, error);
throw new Error(`Failed to delete session: ${error.message}`);
}
},
});
}
2 changes: 2 additions & 0 deletions src/tools/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { FastMCP } from 'fastmcp/dist/FastMCP.js';
import answerAppium from './answerAppium.js';
import createSession from './create-session.js';
import deleteSession from './delete-session.js';
import createCloudSession from './create-cloud-session.js';
import uploadApp from './upload-app.js';
import generateLocators from './locators.js';
Expand All @@ -19,6 +20,7 @@ import terminateApp from './interactions/terminateApp.js';
export default function registerTools(server: FastMCP): void {
selectPlatform(server);
createSession(server);
deleteSession(server);
createCloudSession(server);
uploadApp(server);
generateLocators(server);
Expand Down
50 changes: 49 additions & 1 deletion src/tools/sessionStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ import { XCUITestDriver } from 'appium-xcuitest-driver';

let driver: any = null;
let sessionId: string | null = null;
let isDeletingSession = false; // Lock to prevent concurrent deletion

export function setSession(d: any, id: string) {
export function setSession(d: any, id: string | null) {
driver = d;
sessionId = id;
// Reset deletion flag when setting a new session
if (d && id) {
isDeletingSession = false;
}
}

export function getDriver() {
Expand All @@ -17,6 +22,49 @@ export function getSessionId() {
return sessionId;
}

export function isDeletingSessionInProgress() {
return isDeletingSession;
}

export function hasActiveSession(): boolean {
return driver !== null && sessionId !== null && !isDeletingSession;
}

export async function safeDeleteSession(): Promise<boolean> {
// Check if there's no session to delete
if (!driver || !sessionId) {
console.log('No active session to delete.');
return false;
}

// Check if deletion is already in progress
if (isDeletingSession) {
console.log('Session deletion already in progress, skipping...');
return false;
}

// Set lock
isDeletingSession = true;

try {
console.log('Deleting current session');
await driver.deleteSession();

// Clear the session from store
driver = null;
sessionId = null;

console.log('Session deleted successfully.');
return true;
} catch (error) {
console.error('Error deleting session:', error);
throw error;
} finally {
// Always release lock
isDeletingSession = false;
}
}

export const getPlatformName = (driver: any): string => {
if (driver instanceof AndroidUiautomator2Driver) return 'Android';
if (driver instanceof XCUITestDriver) return 'iOS';
Expand Down