Skip to content

Commit e6ffad0

Browse files
feat: add new tools to get active contexts and switch context (#51)
1 parent 61bc03e commit e6ffad0

File tree

5 files changed

+174
-2
lines changed

5 files changed

+174
-2
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,13 @@ MCP Appium provides a comprehensive set of tools organized into the following ca
178178
| `create_session` | Create a new mobile automation session for Android or iOS |
179179
| `delete_session` | Delete the current mobile session and clean up resources |
180180

181+
### Context Management
182+
183+
| Tool | Description |
184+
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
185+
| `appium_get_contexts` | Get all available contexts in the current Appium session. Returns a list of context names including NATIVE_APP and any webview contexts (e.g., WEBVIEW_<id> or WEBVIEW_<package>). |
186+
| `appium_switch_context` | Switch to a specific context in the Appium session. Use this to switch between native app context (NATIVE_APP) and webview contexts (WEBVIEW_<id> or WEBVIEW_<package>). Use appium_get_contexts to see available contexts first. |
187+
181188
### Element Discovery & Interaction
182189

183190
| Tool | Description |

src/session-store.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ let driver: any = null;
66
let sessionId: string | null = null;
77
let isDeletingSession = false; // Lock to prevent concurrent deletion
88

9+
export const PLATFORM = {
10+
android: 'Android',
11+
ios: 'iOS',
12+
};
13+
914
export function setSession(d: any, id: string | null) {
1015
driver = d;
1116
sessionId = id;
@@ -67,7 +72,7 @@ export async function safeDeleteSession(): Promise<boolean> {
6772
}
6873

6974
export const getPlatformName = (driver: any): string => {
70-
if (driver instanceof AndroidUiautomator2Driver) return 'Android';
71-
if (driver instanceof XCUITestDriver) return 'iOS';
75+
if (driver instanceof AndroidUiautomator2Driver) return PLATFORM.android;
76+
if (driver instanceof XCUITestDriver) return PLATFORM.ios;
7277
throw new Error('Unknown driver type');
7378
};

src/tools/context/get-contexts.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { FastMCP } from 'fastmcp/dist/FastMCP.js';
2+
import { z } from 'zod';
3+
import { getDriver } from '../../session-store.js';
4+
5+
export default function getContexts(server: FastMCP): void {
6+
server.addTool({
7+
name: 'appium_get_contexts',
8+
description:
9+
'Get all available contexts in the current Appium session. Returns a list of context names including NATIVE_APP and any webview contexts (e.g., WEBVIEW_<id> or WEBVIEW_<package>).',
10+
parameters: z.object({}),
11+
annotations: {
12+
readOnlyHint: true,
13+
openWorldHint: false,
14+
},
15+
execute: async (args: any, context: any): Promise<any> => {
16+
const driver = getDriver();
17+
if (!driver) {
18+
throw new Error('No driver found. Please create a session first.');
19+
}
20+
21+
try {
22+
const [currentContext, contexts] = await Promise.all([
23+
driver.getCurrentContext().catch(() => null),
24+
driver.getContexts().catch(() => []),
25+
]);
26+
27+
if (!contexts || contexts.length === 0) {
28+
return {
29+
content: [
30+
{
31+
type: 'text',
32+
text: 'No contexts available.',
33+
},
34+
],
35+
};
36+
}
37+
38+
return {
39+
content: [
40+
{
41+
type: 'text',
42+
text: `Available contexts: ${JSON.stringify(contexts, null, 2)}\nCurrent context: ${currentContext || 'N/A'}`,
43+
},
44+
],
45+
};
46+
} catch (err: any) {
47+
return {
48+
content: [
49+
{
50+
type: 'text',
51+
text: `Failed to get contexts. Error: ${err.toString()}`,
52+
},
53+
],
54+
isError: true,
55+
};
56+
}
57+
},
58+
});
59+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { FastMCP } from 'fastmcp/dist/FastMCP.js';
2+
import { z } from 'zod';
3+
import { getDriver, getPlatformName, PLATFORM } from '../../session-store.js';
4+
5+
export default function switchContext(server: FastMCP): void {
6+
const schema = z.object({
7+
context: z
8+
.string()
9+
.describe(
10+
'The name of the context to switch to. Common values: "NATIVE_APP" for native context, or "WEBVIEW_<id>" / "WEBVIEW_<package>" for webview contexts.'
11+
),
12+
});
13+
14+
server.addTool({
15+
name: 'appium_switch_context',
16+
description:
17+
'Switch to a specific context in the Appium session. Use this to switch between native app context (NATIVE_APP) and webview contexts (WEBVIEW_<id> or WEBVIEW_<package>). Use appium_get_contexts to see available contexts first.',
18+
parameters: schema,
19+
annotations: {
20+
readOnlyHint: false,
21+
openWorldHint: false,
22+
},
23+
execute: async (args: any, context: any): Promise<any> => {
24+
const driver = getDriver();
25+
if (!driver) {
26+
throw new Error('No driver found. Please create a session first.');
27+
}
28+
29+
try {
30+
const [currentContext, availableContexts] = await Promise.all([
31+
driver.getCurrentContext().catch(() => null),
32+
driver.getContexts().catch(() => []),
33+
]);
34+
35+
if (currentContext === args.context) {
36+
return {
37+
content: [
38+
{
39+
type: 'text',
40+
text: `Already on context "${args.context}".`,
41+
},
42+
],
43+
};
44+
}
45+
46+
if (!availableContexts || availableContexts.length === 0) {
47+
return {
48+
content: [
49+
{
50+
type: 'text',
51+
text: 'No contexts available. Cannot switch context.',
52+
},
53+
],
54+
isError: true,
55+
};
56+
}
57+
58+
if (!availableContexts.includes(args.context)) {
59+
return {
60+
content: [
61+
{
62+
type: 'text',
63+
text: `Context "${args.context}" not found. Available contexts: ${JSON.stringify(availableContexts, null, 2)}`,
64+
},
65+
],
66+
isError: true,
67+
};
68+
}
69+
await driver.switchContext(args.context);
70+
71+
// Verify the switch was successful
72+
const newContext = await driver.getCurrentContext();
73+
74+
return {
75+
content: [
76+
{
77+
type: 'text',
78+
text: `Successfully switched context from "${currentContext || 'N/A'}" to "${newContext}".`,
79+
},
80+
],
81+
};
82+
} catch (err: any) {
83+
return {
84+
content: [
85+
{
86+
type: 'text',
87+
text: `Failed to switch context. Error: ${err.toString()}`,
88+
},
89+
],
90+
isError: true,
91+
};
92+
}
93+
},
94+
});
95+
}

src/tools/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import installApp from './app-management/install-app.js';
3939
import uninstallApp from './app-management/uninstall-app.js';
4040
import terminateApp from './app-management/terminate-app.js';
4141
import listApps from './app-management/list-apps.js';
42+
import getContexts from './context/get-contexts.js';
43+
import switchContext from './context/switch-context.js';
4244

4345
export default function registerTools(server: FastMCP): void {
4446
// Wrap addTool to inject logging around tool execution
@@ -139,6 +141,10 @@ export default function registerTools(server: FastMCP): void {
139141
terminateApp(server);
140142
listApps(server);
141143

144+
// Context Management
145+
getContexts(server);
146+
switchContext(server);
147+
142148
// Test Generation
143149
generateLocators(server);
144150
generateTest(server);

0 commit comments

Comments
 (0)