-
Notifications
You must be signed in to change notification settings - Fork 142
MCP server + agent tooling #3560
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: kn/bmad-project-scan
Are you sure you want to change the base?
Conversation
- Add TypeScript CLI (cli.ts) for shell-based app interaction
- Add AGENT_GUIDE.md with comprehensive MCP tooling docs
- getScreen now returns {testIds, tree} for easier element discovery
- Add startServices MCP tool to launch infrastructure
- Refactor state to .mcp/ dir (logs/, pids/, ready)
- Update .gitignore for .mcp/ and .beads/
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request adds comprehensive MCP (Model Context Protocol) server infrastructure for automated mobile app testing via Appium, enabling AI agents to interact with the Blink React Native app.
Key Changes:
- Added Appium as a development dependency for mobile automation
- Implemented TypeScript MCP server with 11 tools for app interaction (tap, type, swipe, getScreen, etc.)
- Created shell-based orchestration system for managing emulator, Metro, Appium, and app lifecycle
- Added TypeScript CLI tool for manual app interaction
- Comprehensive agent documentation guide (376 lines)
Reviewed changes
Copilot reviewed 38 out of 41 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
| package.json | Added appium ^3.1.2 as devDependency |
| yarn.lock | Added Appium and 133+ transitive dependencies |
| dev/mcp-server/src/* | TypeScript MCP server implementation (tools, client, parsers) |
| dev/mcp/orchestrator.sh | Main service orchestration script |
| dev/mcp/services/*.sh | Individual service startup scripts (emulator, metro, appium, app) |
| dev/mcp/lib/common.sh | Shared utilities for process management and health checks |
| dev/mcp-stop.sh | Cleanup script for all services |
| dev/mcp-start.sh | Legacy startup script (delegates to orchestrator) |
| .mcp.json | MCP server configuration for Claude Code |
| Makefile | Added mcp-* targets for building and managing services |
| .gitignore | Added .mcp/ and .beads/ exclusions |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
dev/mcp-server/src/appium/client.ts
Outdated
| @@ -0,0 +1,80 @@ | |||
| // @ts-nocheck - WebDriverIO types are complex, runtime behavior is correct | |||
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not use "@ts-nocheck" because it alters compilation errors.
| @@ -0,0 +1,77 @@ | |||
| // @ts-nocheck - MCP SDK type inference is complex | |||
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not use "@ts-nocheck" because it alters compilation errors.
| @@ -0,0 +1,71 @@ | |||
| // @ts-nocheck - MCP SDK type inference is complex | |||
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not use "@ts-nocheck" because it alters compilation errors.
| @@ -0,0 +1,50 @@ | |||
| // @ts-nocheck - MCP SDK type inference is complex | |||
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not use "@ts-nocheck" because it alters compilation errors.
| @@ -0,0 +1,39 @@ | |||
| // @ts-nocheck - MCP SDK type inference is complex | |||
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not use "@ts-nocheck" because it alters compilation errors.
| @@ -0,0 +1,55 @@ | |||
| // @ts-nocheck - MCP SDK type inference is complex | |||
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not use "@ts-nocheck" because it alters compilation errors.
| @@ -0,0 +1,53 @@ | |||
| // @ts-nocheck - MCP SDK type inference is complex | |||
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not use "@ts-nocheck" because it alters compilation errors.
dev/mcp/services/appium.sh
Outdated
|
|
||
| # Start Appium | ||
| cd "$PROJECT_DIR" | ||
| start_daemon "appium" "yarn appium --relaxed-security" || { |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Appium server is started with --relaxed-security and no explicit bind address, which typically exposes an unauthenticated Appium API with all security checks disabled on all network interfaces. An attacker on the same network could connect to port 4723 and invoke privileged Appium commands (e.g., filesystem and shell operations) against the emulator and potentially the host. Remove --relaxed-security for normal use, or at minimum bind Appium to 127.0.0.1 and tightly restrict insecure features to trusted, local-only workflows.
Makefile
Outdated
|
|
||
| # Start Appium server standalone | ||
| mcp-appium: | ||
| yarn appium --relaxed-security |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This Makefile target starts Appium with --relaxed-security and no host restriction, which exposes an unauthenticated, security-relaxed Appium instance on port 4723 to the local network. A malicious user who can reach the developer machine could abuse this to drive the emulator and invoke powerful Appium commands, leading to data exfiltration or further compromise. Drop --relaxed-security here or ensure Appium is bound to 127.0.0.1 and that any insecure features are only enabled in tightly controlled local environments.
dev/mcp-start.sh
Outdated
|
|
||
| # 3. Appium | ||
| echo "Starting Appium..." | ||
| nohup yarn appium --relaxed-security > "$LOG_DIR/appium.log" 2>&1 & |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Starting Appium with --relaxed-security and no explicit --address means the server will typically listen on all interfaces with its security checks disabled. This allows anyone on the reachable network to hit port 4723 and use powerful Appium endpoints, potentially interacting with the emulator and accessing sensitive data without authentication. Remove --relaxed-security from this script or, if it is absolutely required, bind Appium to 127.0.0.1 and document that it must never be exposed beyond the local host.
Security: - Bind Appium to 127.0.0.1 (localhost only) - Update all scripts: services/appium.sh, mcp-start.sh, Makefile TypeScript: - client.ts: Remove @ts-nocheck, use proper WebDriverIO types - tap.ts: Replace @ts-nocheck with targeted @ts-expect-error Note: Other tool files still use @ts-nocheck due to MCP SDK's complex recursive Zod type inference. Full migration requires more investigation of SDK type exports. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
The Typesystem fixes are absolut horrible for the agent. It's eating thousand of tokens and probably doesn't help much. Those are ulta complex types. Yes, it can be done but at which costs? And what are the beneefits? |
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 37 out of 40 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // @ts-nocheck - MCP SDK type inference is complex | ||
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| import { z } from "zod"; | ||
| import { AppiumClient } from "../appium/client.js"; | ||
| import { buildSelector } from "../utils/selectors.js"; | ||
|
|
||
| export function registerGetElementTool(server: McpServer, client: AppiumClient) { | ||
| server.tool( | ||
| "getElement", | ||
| "Get detailed info about one specific element by testID", | ||
| { | ||
| id: z.string().describe("Element testID"), | ||
| } as never, | ||
| async ({ id }: { id: string }) => { |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not use "@ts-nocheck" because it alters compilation errors.
| // @ts-nocheck - MCP SDK type inference is complex | |
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | |
| import { z } from "zod"; | |
| import { AppiumClient } from "../appium/client.js"; | |
| import { buildSelector } from "../utils/selectors.js"; | |
| export function registerGetElementTool(server: McpServer, client: AppiumClient) { | |
| server.tool( | |
| "getElement", | |
| "Get detailed info about one specific element by testID", | |
| { | |
| id: z.string().describe("Element testID"), | |
| } as never, | |
| async ({ id }: { id: string }) => { | |
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | |
| import { z } from "zod"; | |
| import { AppiumClient } from "../appium/client.js"; | |
| import { buildSelector } from "../utils/selectors.js"; | |
| const getElementInputSchema = z.object({ | |
| id: z.string().describe("Element testID"), | |
| }); | |
| type GetElementInput = z.infer<typeof getElementInputSchema>; | |
| export function registerGetElementTool(server: McpServer, client: AppiumClient) { | |
| server.tool( | |
| "getElement", | |
| "Get detailed info about one specific element by testID", | |
| getElementInputSchema, | |
| async ({ id }: GetElementInput) => { |
| // @ts-nocheck - MCP SDK type inference is complex | ||
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| import { z } from "zod"; | ||
| import { AppiumClient } from "../appium/client.js"; | ||
| import { parsePageSource, collectTestIds, type FilterType } from "../utils/xml-parser.js"; | ||
|
|
||
| export function registerGetScreenTool(server: McpServer, client: AppiumClient) { | ||
| server.tool( | ||
| "getScreen", | ||
| "Get structured representation of all visible elements as JSON. Primary tool for understanding app state.", | ||
| { | ||
| maxDepth: z.number().optional().describe("Max nesting depth (default: 10)"), | ||
| filter: z.enum(["all", "interactive", "text"]).optional().describe("Filter: all, interactive, or text"), | ||
| } as never, | ||
| async ({ maxDepth = 10, filter = "all" }: { maxDepth?: number; filter?: string }) => { |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not use "@ts-nocheck" because it alters compilation errors.
| // @ts-nocheck - MCP SDK type inference is complex | |
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | |
| import { z } from "zod"; | |
| import { AppiumClient } from "../appium/client.js"; | |
| import { parsePageSource, collectTestIds, type FilterType } from "../utils/xml-parser.js"; | |
| export function registerGetScreenTool(server: McpServer, client: AppiumClient) { | |
| server.tool( | |
| "getScreen", | |
| "Get structured representation of all visible elements as JSON. Primary tool for understanding app state.", | |
| { | |
| maxDepth: z.number().optional().describe("Max nesting depth (default: 10)"), | |
| filter: z.enum(["all", "interactive", "text"]).optional().describe("Filter: all, interactive, or text"), | |
| } as never, | |
| async ({ maxDepth = 10, filter = "all" }: { maxDepth?: number; filter?: string }) => { | |
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | |
| import { z } from "zod"; | |
| import { AppiumClient } from "../appium/client.js"; | |
| import { parsePageSource, collectTestIds, type FilterType } from "../utils/xml-parser.js"; | |
| export function registerGetScreenTool(server: McpServer, client: AppiumClient) { | |
| (server as any).tool( | |
| "getScreen", | |
| "Get structured representation of all visible elements as JSON. Primary tool for understanding app state.", | |
| { | |
| maxDepth: z.number().optional().describe("Max nesting depth (default: 10)"), | |
| filter: z.enum(["all", "interactive", "text"]).optional().describe("Filter: all, interactive, or text"), | |
| } as never, | |
| async (args: any) => { | |
| const { maxDepth = 10, filter = "all" } = args ?? {}; |
| @@ -0,0 +1,55 @@ | |||
| // @ts-nocheck - MCP SDK type inference is complex | |||
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not use "@ts-nocheck" because it alters compilation errors.
| // @ts-nocheck - MCP SDK type inference is complex | |
| // MCP SDK type inference is complex |
| @@ -0,0 +1,55 @@ | |||
| // @ts-nocheck - MCP SDK type inference is complex | |||
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not use "@ts-nocheck" because it alters compilation errors.
| // @ts-nocheck - MCP SDK type inference is complex | |
| // MCP SDK type inference is complex |
- Revert .gitattributes (remove beads merge driver) - Remove dev/mcp/app-cli.sh (replaced by TypeScript CLI) - Remove dev/mcp-start.sh (replaced by orchestrator.sh) - Remove dev/start-all.sh (backwards compat wrapper) - Move AGENT_GUIDE.md to dev/ for better visibility - Update mcp-stop.sh comment 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Simple wrapper: ./dev/app tap "Login" - Update AGENT_GUIDE.md with new CLI usage 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 34 out of 37 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // @ts-nocheck - MCP SDK type inference is complex | ||
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| import { z } from "zod"; | ||
| import { AppiumClient } from "../appium/client.js"; | ||
| import { parsePageSource, collectTestIds, type FilterType } from "../utils/xml-parser.js"; | ||
|
|
||
| export function registerGetScreenTool(server: McpServer, client: AppiumClient) { |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not use "@ts-nocheck" because it alters compilation errors.
| // @ts-nocheck - MCP SDK type inference is complex | |
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | |
| import { z } from "zod"; | |
| import { AppiumClient } from "../appium/client.js"; | |
| import { parsePageSource, collectTestIds, type FilterType } from "../utils/xml-parser.js"; | |
| export function registerGetScreenTool(server: McpServer, client: AppiumClient) { | |
| // Note: MCP SDK type inference is complex and may not align perfectly with our zod schemas. | |
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | |
| import { z } from "zod"; | |
| import { AppiumClient } from "../appium/client.js"; | |
| import { parsePageSource, collectTestIds, type FilterType } from "../utils/xml-parser.js"; | |
| export function registerGetScreenTool(server: McpServer, client: AppiumClient) { | |
| // @ts-expect-error - MCP SDK tool typing is not fully compatible with this zod-based schema definition |
- split cli.ts into basic, ux, helpers, config - simplify ui command output (flat list, -j for JSON) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- ux login waits for user to solve captcha (2min timeout) - polls for Geetest element, auto-continues when solved - use waitForElement instead of sleep+verify for reliability - comprehensive AGENT_GUIDE update with all CLI docs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- app launch/kill/restart/clear/info commands - adb() now throws on failure, adbSafe() for graceful fallback - update AGENT_GUIDE with new commands 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 42 out of 46 changed files in this pull request and generated 11 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -0,0 +1,53 @@ | |||
| // @ts-nocheck - MCP SDK type inference is complex | |||
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TypeScript strict mode is enabled but @ts-nocheck suppresses all type checking in this file. This defeats the purpose of TypeScript. Instead, fix the actual type issues or use targeted @ts-expect-error comments.
| # --relaxed-security: required for shell commands (hot reload) and app management | ||
| start_daemon "appium" "yarn appium --address 127.0.0.1 --relaxed-security" || { |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Appium server is started with --relaxed-security flag which disables important security checks. This is documented as required for shell commands, but exposes the server to potential abuse. Consider documenting specific security implications and ensuring the server is bound to localhost only (which is done correctly via --address 127.0.0.1).
| # 4. Blink app (install + launch) | ||
|
|
||
| set -euo pipefail | ||
| source "$(dirname "$0")/lib/common.sh" |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The script sources common.sh using a relative path derived from the script's directory. If the script is invoked via symlink, this could potentially source the wrong file. Consider using realpath to resolve symlinks before sourcing.
| echo "=== $name started at $(date) ===" >> "$log_file" | ||
|
|
||
| # Start with nohup, redirect output | ||
| nohup bash -c "$cmd" >> "$log_file" 2>&1 & |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shell command is constructed by concatenating user input without proper escaping. Although cmd is currently hardcoded in calling contexts, this pattern is risky. Consider using array-based command execution or explicit quoting.
| export function parsePageSource( | ||
| xml: string, | ||
| options: { maxDepth?: number; filter?: FilterType } = {}, | ||
| ): ElementNode | null { | ||
| const { maxDepth = 10, filter = "all" } = options; | ||
|
|
||
| try { | ||
| const parsed = parser.parse(xml); | ||
|
|
||
| // Find root element (usually hierarchy or android.widget.FrameLayout) | ||
| const rootKey = Object.keys(parsed).find((k) => !k.startsWith("?")); | ||
| if (!rootKey) return null; | ||
|
|
||
| return convertNode( | ||
| parsed as Record<string, unknown>, | ||
| filter, | ||
| 0, | ||
| maxDepth, | ||
| ); | ||
| } catch (error) { | ||
| console.error("Failed to parse page source:", error); | ||
| return null; | ||
| } |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The XML parser converts attribute names but doesn't validate or sanitize the input. If malicious XML is provided (e.g., from a compromised app or emulator), this could lead to unexpected behavior. Consider adding XML validation or size limits.
| const timeout = setTimeout(() => { | ||
| proc.kill(); | ||
| resolve({ | ||
| content: [ | ||
| { | ||
| type: "text", | ||
| text: JSON.stringify({ | ||
| success: false, | ||
| error: "Timeout after 5 minutes", | ||
| output: stdout.slice(-2000), | ||
| }, null, 2), | ||
| }, | ||
| ], | ||
| isError: true, | ||
| }); | ||
| }, 300000); |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 5-minute timeout is hardcoded. For slower systems or cold emulator boots, this may be insufficient. Consider making this configurable via environment variable or adding a warning in documentation about expected timing.
dev/mcp-server/src/cli/basic.ts
Outdated
| @@ -0,0 +1,160 @@ | |||
| import { execSync } from "child_process"; | |||
| import { Command } from "commander"; | |||
| import { adb, adbSafe, tapElement, typeText, getUiHierarchy, collectTestIds, collectUiInfo } from "./helpers.js"; | |||
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'collectTestIds' is defined but never used.
| import { adb, adbSafe, tapElement, typeText, getUiHierarchy, collectTestIds, collectUiInfo } from "./helpers.js"; | |
| import { adb, adbSafe, tapElement, typeText, getUiHierarchy, collectUiInfo } from "./helpers.js"; |
dev/mcp-server/src/cli/ux.ts
Outdated
| hasElement, | ||
| waitForElement, | ||
| waitForAny, | ||
| verifyScreen, |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'verifyScreen' is defined but never used.
| verifyScreen, |
dev/mcp-server/src/cli/ux.ts
Outdated
| waitForElement, | ||
| waitForAny, | ||
| verifyScreen, | ||
| getElementText, |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'getElementText' is defined but never used.
| getElementText, |
dev/mcp-server/src/cli/ux.ts
Outdated
| import { | ||
| adb, | ||
| sleep, | ||
| goHome, | ||
| tapElement, | ||
| tapAndWait, | ||
| typeText, | ||
| clearInput, | ||
| hasElement, | ||
| waitForElement, | ||
| waitForAny, | ||
| verifyScreen, | ||
| getElementText, | ||
| getUiHierarchy, | ||
| collectTestIds, | ||
| collectUiInfo, | ||
| } from "./helpers.js"; |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused imports getElementText, verifyScreen.
Summary
Commits
Screenshot
🤖 Generated with Claude Code
Appendix: Design Decisions
Why not mobile-mcp?
mobile-mcp is an alternative MCP server for mobile automation. After research, here's why we built a custom Appium-based solution:
What mobile-mcp uses under the hood:
xcrun simctlLimitations for Blink's use case:
waitForDisplayedwith configurable timeout~testID, XPath)~idselector (W3C standard)mobile: shellKey risk with mobile-mcp: Coordinate-based taps break when:
Our WebDriver approach finds elements by accessibility ID regardless of position, making tests more resilient.
Why Appium instead of BrowserStack/cloud solutions?
BrowserStack and similar cloud services offer real device farms with parallel testing. However:
Why local Appium is better for our use case:
Development workflow: We need fast iteration on a local emulator (~35s cold boot). Cloud round-trips add latency.
Cost: Cloud device farms charge per minute. Development sessions can run hours.
MCP integration: Local Appium allows direct stdio MCP transport. Cloud would require additional networking/auth complexity.
Simplicity: Single-developer workflow doesn't need distributed device management.
What BrowserStack adds (that we don't need yet):
When to consider cloud: If/when we need CI testing across multiple real devices, BrowserStack/AWS Device Farm integration would make sense. The Appium tests we write locally would work unchanged on cloud infrastructure.
Why Appium specifically?
Appium's UiAutomator2 driver provides capabilities beyond basic ADB:
Our implementation currently uses a subset (element finding, tapping, typing, screenshots) but can expand to use these advanced features as needed.
Sources