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
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Build (tsup)
run: npm run build:tsup
- name: Bundle AXe artifacts
run: npm run bundle:axe

- name: Build (Smithery)
run: npm run build

- name: Verify Smithery bundle
run: npm run verify:smithery-bundle

- name: Lint
run: npm run lint

Expand Down
3 changes: 0 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ jobs:
- name: Bundle AXe artifacts
run: npm run bundle:axe

- name: Build TypeScript (tsup)
run: npm run build:tsup

- name: Build Smithery bundle
run: npm run build

Expand Down
268 changes: 134 additions & 134 deletions .smithery/index.cjs

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ Leave this unset for the streamlined session-aware experience; enable it to forc

If you do not wish to send error logs to Sentry, set `XCODEBUILDMCP_SENTRY_DISABLED=true`.

## AXe binary override

UI automation and simulator video capture require the AXe binary. By default, XcodeBuildMCP uses the bundled AXe when available, then falls back to `PATH`. To force a specific binary location, set `XCODEBUILDMCP_AXE_PATH` (preferred). `AXE_PATH` is also recognized for compatibility.

Example:

```
XCODEBUILDMCP_AXE_PATH=/opt/axe/bin/axe
```

## Related docs
- Session defaults: [SESSION_DEFAULTS.md](SESSION_DEFAULTS.md)
- Tools reference: [TOOLS.md](TOOLS.md)
Expand Down
6 changes: 6 additions & 0 deletions docs/TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ It reports on:

## Common issues

### UI automation reports missing AXe
UI automation (describe/tap/swipe/type) and simulator video capture require the AXe binary. If you see a missing AXe error:
- Ensure `bundled/` artifacts exist when installing from Smithery or npm.
- Or set `XCODEBUILDMCP_AXE_PATH` to a known AXe binary path (preferred), or `AXE_PATH`.
- Re-run the doctor tool to confirm AXe is detected.

### Tool timeouts
Some clients have short tool timeouts. If you see timeouts, increase the client timeout (for example, `tool_timeout_sec = 600` in Codex).

Expand Down
68 changes: 68 additions & 0 deletions docs/investigations/issue-163.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Investigation: UI automation tools unavailable with Smithery install (issue #163)

## Summary
Smithery installs ship only the compiled entrypoint, while the server hard-requires a bundled `bundled/axe` path derived from `process.argv[1]`. This makes UI automation (and simulator video capture) fail even when system `axe` exists on PATH, and Doctor can report contradictory statuses.

## Symptoms
- UI automation tools (`describe_ui`, `tap`, `swipe`, etc.) fail with "Bundled axe tool not found. UI automation features are not available."
- `doctor` reports system axe present, but UI automation unavailable due to missing bundled binary.
- Smithery cache lacks `bundled/axe` directory; only `index.cjs`, `manifest.json`, `.metadata.json` present.

## Investigation Log

### 2026-01-06 - Initial Assessment
**Hypothesis:** Smithery packaging omits bundled binaries and server does not fallback to system axe.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor grammar issue: "fallback" should be "fall back".

The word "fallback" is a noun, but here it's used as a verb. Consider changing to "does not fall back to system axe".

🔎 Suggested fix
-**Hypothesis:** Smithery packaging omits bundled binaries and server does not fallback to system axe.
+**Hypothesis:** Smithery packaging omits bundled binaries and server does not fall back to system axe.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
**Hypothesis:** Smithery packaging omits bundled binaries and server does not fallback to system axe.
**Hypothesis:** Smithery packaging omits bundled binaries and server does not fall back to system axe.
🧰 Tools
🪛 LanguageTool

[grammar] ~14-~14: The word “fallback” is a noun. The verb is spelled with a space.
Context: ...ts bundled binaries and server does not fallback to system axe. Findings: Issue repo...

(NOUN_VERB_CONFUSION)

🤖 Prompt for AI Agents
In @docs/investigations/issue-163.md at line 14, Update the sentence "Smithery
packaging omits bundled binaries and server does not fallback to system axe." by
changing the verb "fallback" to the two-word verb form "fall back" so it reads
"server does not fall back to system axe", ensuring correct grammar in the
hypothesis line.

**Findings:** Issue report indicates bundled path is computed relative to `process.argv[1]` and Smithery cache lacks `bundled/`.
**Evidence:** GitHub issue #163 body (Smithery cache contents; bundled path logic).
**Conclusion:** Needs code and packaging investigation.

### 2026-01-06 - AXe path resolution and bundled-only assumption
**Hypothesis:** AXe resolution is bundled-only, so missing `bundled/axe` disables tools regardless of PATH.
**Findings:** `getAxePath()` computes `bundledAxePath` from `process.argv[1]` and returns it only if it exists; otherwise `null`. No PATH or env override.
**Evidence:** `src/utils/axe-helpers.ts:15-36`
**Conclusion:** Confirmed. Smithery layout lacking `bundled/` will always return null.

### 2026-01-06 - UI automation and video capture gating
**Hypothesis:** UI tools and video capture preflight fail when `getAxePath()` returns null.
**Findings:** UI tools call `getAxePath()` and throw `DependencyError` if absent; `record_sim_video` preflights `areAxeToolsAvailable()` and `isAxeAtLeastVersion()`; `startSimulatorVideoCapture` returns error if `getAxePath()` is null.
**Evidence:** `src/mcp/tools/ui-testing/describe_ui.ts:150-164`, `src/mcp/tools/simulator/record_sim_video.ts:80-88`, `src/utils/video_capture.ts:92-99`
**Conclusion:** Confirmed. Missing bundled binary blocks all UI automation and simulator video capture.

### 2026-01-06 - Doctor output inconsistency
**Hypothesis:** Doctor uses different checks for dependency presence vs feature availability.
**Findings:** Doctor uses `areAxeToolsAvailable()` (bundled-only) for UI automation feature status, while dependency check can succeed via `which axe` when bundled is missing.
**Evidence:** `src/mcp/tools/doctor/doctor.ts:49-68`, `src/mcp/tools/doctor/lib/doctor.deps.ts:100-132`
**Conclusion:** Confirmed. Doctor can report `axe` dependency present but UI automation unsupported.

### 2026-01-06 - Packaging/Smithery artifact mismatch
**Hypothesis:** NPM releases include `bundled/`, Smithery builds do not.
**Findings:** `bundle:axe` creates `bundled/` and npm packaging includes it, but Smithery config has no asset inclusion hints. Release workflow bundles AXe before publish.
**Evidence:** `package.json:21-44`, `.github/workflows/release.yml:48-55`, `smithery.yaml:1-3`, `smithery.config.js:1-6`
**Conclusion:** Confirmed. Smithery build output likely omits bundled artifacts unless explicitly configured.

### 2026-01-06 - Smithery local server deployment flow
**Hypothesis:** Smithery deploys local servers from GitHub pushes and expects build-time packaging to include assets.
**Findings:** README install flow uses Smithery CLI; `smithery.yaml` targets `local`. `bundled/` is gitignored, so it must be produced during Smithery’s deployment build. Current `npm run build` does not run `bundle:axe`.
**Evidence:** `README.md:11-74`, `smithery.yaml:1-3`, `.github/workflows/release.yml:48-62`, `.gitignore:66-68`
**Conclusion:** Confirmed. Smithery deploy must run `bundle:axe` and explicitly include `bundled/` in the produced bundle.

### 2026-01-06 - Smithery config constraints and bundling workaround
**Hypothesis:** Adding esbuild plugins in `smithery.config.js` overrides Smithery’s bootstrap plugin.
**Findings:** Smithery CLI merges config via spread and replaces `plugins`, causing `virtual:bootstrap` resolution to fail when custom plugins are supplied. Side-effect bundling in `smithery.config.js` avoids plugin override and can copy `bundled/` into `.smithery/`.
**Evidence:** `node_modules/@smithery/cli/dist/index.js:~2716600-2717500`, `smithery.config.js:1-47`
**Conclusion:** Confirmed. Bundling must run outside esbuild plugins; Linux builders must skip binary verification.

## Root Cause
Two coupled assumptions break Smithery installs:
1) `getAxePath()` is bundled-only and derives the path from `process.argv[1]`, which points into Smithery’s cache (missing `bundled/axe`), so it always returns null.
2) Smithery packaging does not include the `bundled/` directory, so the bundled-only resolver can never succeed under Smithery even if AXe is installed system-wide.

## Recommendations
1. Add a robust AXe resolver: allow explicit env override and PATH fallback; keep bundled as preferred but not exclusive.
2. Distinguish bundled vs system AXe in UI tools and video capture; only apply bundled-specific env when the bundled binary is used.
3. Align Doctor output: show both bundled availability and PATH availability, and use that in the UI automation supported status.
4. Update Smithery build to run `bundle:axe` and copy `bundled/` into the Smithery bundle output; skip binary verification on non-mac builders to avoid build failures.

## Preventive Measures
- Add tests for AXe resolution precedence (bundled, env override, PATH) and for Doctor output consistency.
- Document Smithery-specific install requirements and verify `bundled/` presence in Smithery artifacts during CI.
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@
"mcpName": "com.xcodebuildmcp/XcodeBuildMCP",
"iOSTemplateVersion": "v1.0.8",
"macOSTemplateVersion": "v1.0.5",
"main": "build/index.js",
"type": "module",
"module": "src/smithery.ts",
"exports": {
".": "./build/index.js",
"./package.json": "./package.json"
},
"bin": {
"xcodebuildmcp": "build/index.js",
"xcodebuildmcp-doctor": "build/doctor-cli.js"
},
"scripts": {
"build": "npm run generate:version && npm run generate:loaders && npx smithery build",
"build": "npm run build:tsup && npm run build:smithery",
"build:smithery": "npx smithery build src/smithery.ts",
"dev": "npm run generate:version && npm run generate:loaders && npx smithery dev",
"build:tsup": "npm run generate:version && npm run generate:loaders && tsup",
"dev:tsup": "npm run build:tsup && tsup --watch",
Expand All @@ -24,6 +27,7 @@
"format": "prettier --write 'src/**/*.{js,ts}'",
"format:check": "prettier --check 'src/**/*.{js,ts}'",
"typecheck": "npx tsc --noEmit",
"verify:smithery-bundle": "bash scripts/verify-smithery-bundle.sh",
"inspect": "npx @modelcontextprotocol/inspector node build/index.js",
"doctor": "node build/doctor-cli.js",
"tools": "npx tsx scripts/tools-cli.ts",
Expand Down
26 changes: 16 additions & 10 deletions scripts/bundle-axe.sh
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ else
tar -xzf "axe-release.tar.gz"

# Find the extracted directory (might be named differently)
EXTRACTED_DIR=$(find . -type d -name "*AXe*" -o -name "*axe*" | head -1)
EXTRACTED_DIR=$(find . -type d \( -name "*AXe*" -o -name "*axe*" \) | head -1)
if [ -z "$EXTRACTED_DIR" ]; then
# If no AXe directory found, assume files are in current directory
EXTRACTED_DIR="."
Expand Down Expand Up @@ -144,17 +144,23 @@ echo "📦 Copied $FRAMEWORK_COUNT frameworks"
echo "🔍 Bundled frameworks:"
ls -la "$BUNDLED_DIR/Frameworks/"

# Verify binary can run with bundled frameworks
echo "🧪 Testing bundled AXe binary..."
if DYLD_FRAMEWORK_PATH="$BUNDLED_DIR/Frameworks" "$BUNDLED_DIR/axe" --version > /dev/null 2>&1; then
echo "✅ Bundled AXe binary test passed"
# Verify binary can run with bundled frameworks (macOS only)
OS_NAME="$(uname -s)"
if [ "$OS_NAME" = "Darwin" ]; then
echo "🧪 Testing bundled AXe binary..."
if DYLD_FRAMEWORK_PATH="$BUNDLED_DIR/Frameworks" "$BUNDLED_DIR/axe" --version > /dev/null 2>&1; then
echo "✅ Bundled AXe binary test passed"
else
echo "❌ Bundled AXe binary test failed"
exit 1
fi

# Get AXe version for logging
AXE_VERSION=$(DYLD_FRAMEWORK_PATH="$BUNDLED_DIR/Frameworks" "$BUNDLED_DIR/axe" --version 2>/dev/null || echo "unknown")
else
echo "❌ Bundled AXe binary test failed"
exit 1
echo "⚠️ Skipping AXe binary verification on non-macOS (detected $OS_NAME)"
AXE_VERSION="unknown (verification skipped)"
fi

# Get AXe version for logging
AXE_VERSION=$(DYLD_FRAMEWORK_PATH="$BUNDLED_DIR/Frameworks" "$BUNDLED_DIR/axe" --version 2>/dev/null || echo "unknown")
echo "📋 AXe version: $AXE_VERSION"

# Clean up temp directory if it was used
Expand Down
35 changes: 35 additions & 0 deletions scripts/verify-smithery-bundle.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env bash

set -euo pipefail

PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BUNDLE_DIR="$PROJECT_ROOT/.smithery/bundled"
AXE_BIN="$BUNDLE_DIR/axe"
FRAMEWORK_DIR="$BUNDLE_DIR/Frameworks"

if [ ! -f "$AXE_BIN" ]; then
echo "❌ Missing AXe binary at $AXE_BIN"
if [ -d "$PROJECT_ROOT/.smithery" ]; then
echo "🔍 .smithery contents:"
ls -la "$PROJECT_ROOT/.smithery"
fi
exit 1
fi

if [ ! -d "$FRAMEWORK_DIR" ]; then
echo "❌ Missing Frameworks directory at $FRAMEWORK_DIR"
if [ -d "$BUNDLE_DIR" ]; then
echo "🔍 bundled contents:"
ls -la "$BUNDLE_DIR"
fi
exit 1
fi

FRAMEWORK_COUNT="$(find "$FRAMEWORK_DIR" -maxdepth 2 -type d -name "*.framework" | wc -l | tr -d ' ')"
if [ "$FRAMEWORK_COUNT" -eq 0 ]; then
echo "❌ No frameworks found in $FRAMEWORK_DIR"
find "$FRAMEWORK_DIR" -maxdepth 2 -type d | head -n 50
exit 1
fi

echo "✅ Smithery bundle includes AXe binary and $FRAMEWORK_COUNT frameworks"
33 changes: 33 additions & 0 deletions smithery.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,36 @@
import { execFileSync } from 'child_process';
import { cpSync, existsSync, mkdirSync } from 'fs';
import { dirname, join, resolve } from 'path';

const projectRoot = process.cwd();
const bundledDir = join(projectRoot, 'bundled');
const bundledAxePath = join(bundledDir, 'axe');

function resolveOutputDir() {
const args = process.argv;
const outIndex = args.findIndex((arg) => arg === '--out' || arg === '-o');
if (outIndex !== -1 && args[outIndex + 1]) {
return dirname(resolve(args[outIndex + 1]));
}
return join(projectRoot, '.smithery');
}

const outputDir = resolveOutputDir();
const bundledTargetDir = join(outputDir, 'bundled');

if (!existsSync(bundledAxePath)) {
execFileSync('bash', [join(projectRoot, 'scripts', 'bundle-axe.sh')], {
stdio: 'inherit',
});
}

if (existsSync(bundledAxePath)) {
mkdirSync(outputDir, { recursive: true });
cpSync(bundledDir, bundledTargetDir, { recursive: true });
} else {
throw new Error(`AXe bundle missing at ${bundledAxePath}`);
}

export default {
esbuild: {
format: 'cjs',
Expand Down
26 changes: 21 additions & 5 deletions src/mcp/tools/doctor/lib/doctor.deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
collectToolNames,
resolveSelectedWorkflows,
} from '../../../../utils/workflow-selection.ts';
import { areAxeToolsAvailable } from '../../../../utils/axe/index.ts';
import { areAxeToolsAvailable, resolveAxeBinary } from '../../../../utils/axe/index.ts';
import {
isXcodemakeEnabled,
isXcodemakeAvailable,
Expand Down Expand Up @@ -98,9 +98,26 @@ export interface DoctorDependencies {
export function createDoctorDependencies(executor: CommandExecutor): DoctorDependencies {
const binaryChecker: BinaryChecker = {
async checkBinaryAvailability(binary: string) {
// If bundled axe is available, reflect that in dependencies even if not on PATH
if (binary === 'axe' && areAxeToolsAvailable()) {
return { available: true, version: 'Bundled' };
if (binary === 'axe') {
const axeBinary = resolveAxeBinary();
if (!axeBinary) {
return { available: false };
}

let version: string | undefined;
try {
const res = await executor([axeBinary.path, '--version'], 'Get AXe Version');
if (res.success && res.output) {
version = res.output.trim();
}
} catch {
// ignore
}

return {
available: true,
version: version ?? 'Available (version info not available)',
};
}
try {
const which = await executor(['which', binary], 'Check Binary Availability');
Expand All @@ -113,7 +130,6 @@ export function createDoctorDependencies(executor: CommandExecutor): DoctorDepen

let version: string | undefined;
const versionCommands: Record<string, string> = {
axe: 'axe --version',
mise: 'mise --version',
};

Expand Down
4 changes: 2 additions & 2 deletions src/mcp/tools/ui-testing/__tests__/button.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ describe('Button Plugin', () => {
content: [
{
type: 'text',
text: 'Bundled axe tool not found. UI automation features are not available.\n\nThis is likely an installation issue with the npm package.\nPlease reinstall xcodebuildmcp or report this issue.',
text: 'AXe tool not found. UI automation features are not available.\n\nInstall AXe (brew tap cameroncooke/axe && brew install axe) or set XCODEBUILDMCP_AXE_PATH.\nIf you installed via Smithery, ensure bundled artifacts are included or PATH is configured.',
},
],
isError: true,
Expand All @@ -346,7 +346,7 @@ describe('Button Plugin', () => {
content: [
{
type: 'text',
text: 'Bundled axe tool not found. UI automation features are not available.\n\nThis is likely an installation issue with the npm package.\nPlease reinstall xcodebuildmcp or report this issue.',
text: 'AXe tool not found. UI automation features are not available.\n\nInstall AXe (brew tap cameroncooke/axe && brew install axe) or set XCODEBUILDMCP_AXE_PATH.\nIf you installed via Smithery, ensure bundled artifacts are included or PATH is configured.',
},
],
isError: true,
Expand Down
4 changes: 2 additions & 2 deletions src/mcp/tools/ui-testing/__tests__/describe_ui.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ describe('Describe UI Plugin', () => {
content: [
{
type: 'text',
text: 'Bundled axe tool not found. UI automation features are not available.\n\nThis is likely an installation issue with the npm package.\nPlease reinstall xcodebuildmcp or report this issue.',
text: 'AXe tool not found. UI automation features are not available.\n\nInstall AXe (brew tap cameroncooke/axe && brew install axe) or set XCODEBUILDMCP_AXE_PATH.\nIf you installed via Smithery, ensure bundled artifacts are included or PATH is configured.',
},
],
isError: true,
Expand All @@ -142,7 +142,7 @@ describe('Describe UI Plugin', () => {
content: [
{
type: 'text',
text: 'Bundled axe tool not found. UI automation features are not available.\n\nThis is likely an installation issue with the npm package.\nPlease reinstall xcodebuildmcp or report this issue.',
text: 'AXe tool not found. UI automation features are not available.\n\nInstall AXe (brew tap cameroncooke/axe && brew install axe) or set XCODEBUILDMCP_AXE_PATH.\nIf you installed via Smithery, ensure bundled artifacts are included or PATH is configured.',
},
],
isError: true,
Expand Down
4 changes: 2 additions & 2 deletions src/mcp/tools/ui-testing/__tests__/gesture.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ describe('Gesture Plugin', () => {
content: [
{
type: 'text',
text: 'Bundled axe tool not found. UI automation features are not available.\n\nThis is likely an installation issue with the npm package.\nPlease reinstall xcodebuildmcp or report this issue.',
text: 'AXe tool not found. UI automation features are not available.\n\nInstall AXe (brew tap cameroncooke/axe && brew install axe) or set XCODEBUILDMCP_AXE_PATH.\nIf you installed via Smithery, ensure bundled artifacts are included or PATH is configured.',
},
],
isError: true,
Expand All @@ -346,7 +346,7 @@ describe('Gesture Plugin', () => {
content: [
{
type: 'text',
text: 'Bundled axe tool not found. UI automation features are not available.\n\nThis is likely an installation issue with the npm package.\nPlease reinstall xcodebuildmcp or report this issue.',
text: 'AXe tool not found. UI automation features are not available.\n\nInstall AXe (brew tap cameroncooke/axe && brew install axe) or set XCODEBUILDMCP_AXE_PATH.\nIf you installed via Smithery, ensure bundled artifacts are included or PATH is configured.',
},
],
isError: true,
Expand Down
Loading
Loading