keyboard-ext-ax-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@keyboard-ext-ax-mcpSnapshot keyboard extension com.example.keyboard in simulator 2D78332B"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
KeyboardExtAX

Fast, read-only accessibility snapshots of active third-party iOS keyboard extensions in Simulator.
Why this project exists
A custom iOS keyboard runs in a separate app-extension process. When a client app presents that keyboard, ordinary UI automation usually sees the client application's accessibility tree but not the keyboard's useful descendants. The entire keyboard may appear as one opaque element.
That creates a practical automation gap:
screenshots can show the keyboard, but extracting reliable tap coordinates from pixels is slow and brittle;
the client application's accessibility snapshot does not necessarily expose the extension's keys and controls;
starting a new XCTest session for every query is expensive and can disturb keyboard focus;
XCTest element handles become stale whenever the keyboard changes layout;
automation agents should not need to manage test plans, ports, build products, or runner processes.
KeyboardExtAX fills that gap. It attaches XCTest directly to the already-running keyboard extension by bundle identifier, parses its accessibility hierarchy, and returns plain JSON with absolute logical coordinates. A persistent runner keeps warm snapshots fast, while a small controller owns building, caching, ports, and per-simulator lifecycle.
KeyboardExtAX observes only. Use a separate automation tool, such as XcodeBuildMCP, to launch apps, focus text fields, and perform gestures with the returned coordinates.
Related MCP server: ios-sim-mcp
Quick start: MCP
Configure keyboard-ext-ax-mcp as a stdio MCP server. Most MCP clients use a configuration shaped like this:
{
"mcpServers": {
"KeyboardExtAX": {
"command": "keyboard-ext-ax-mcp",
"env": {
"DEVELOPER_DIR": "/Applications/Xcode.app/Contents/Developer"
}
}
}
}If the MCP client does not inherit your shell PATH, replace the command with the absolute path printed by command -v keyboard-ext-ax-mcp.
The server exposes one tool:
keyboard_snapshot(
simulator_id: "<SIMULATOR_UDID>",
extension_bundle_id: "com.example.keyboard.extension",
raw: false
)Example input:
{
"simulator_id": "2D78332B-A3C3-4CEE-962B-99EF531FD201",
"extension_bundle_id": "com.example.keyboard.extension"
}The result is returned as structured MCP content. The MCP layer delegates directly to the same controller used by the CLI, so build caching, sessions, errors, and output are identical.
Quick start: CLI
With the client text field focused and the requested keyboard visible:
keyboard-ext-ax snapshot \
--simulator <SIMULATOR_UDID> \
--extension com.example.keyboard.extensionAdd --output /tmp/keyboard.json to write the result to a file. Add --raw for the nested tree and XCTest diagnostics.
Scope and limitations
KeyboardExtAX currently supports:
macOS hosts;
iOS Simulator;
third-party keyboard extensions;
Xcode 26.5;
read-only accessibility snapshots.
It does not:
tap, swipe, or type;
launch or foreground the client app;
select or enable a keyboard for the user;
access physical iOS devices;
guarantee compatibility with widgets, Live Activities, or other extension types;
guarantee parser compatibility with untested Xcode versions.
The parser reads XCUIApplication.debugDescription. Its text format is not a documented compatibility contract, so each Xcode version must be validated before support is claimed.
Requirements
macOS
Xcode 26.5 with the desired iOS Simulator runtime installed
Python 3.10 or newer
A keyboard app and extension already installed in the target simulator
The keyboard enabled in Settings → General → Keyboard → Keyboards
A foreground client app with a focused text field
If multiple Xcode installations are present, select one with DEVELOPER_DIR or --developer-dir:
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/DeveloperInstallation
Homebrew (recommended)
brew install nikitosina/tap/keyboard-ext-axPyPI with uv
Install both executables permanently:
uv tool install keyboard-ext-axOr run the MCP server without a persistent installation:
uvx keyboard-ext-ax mcpFrom source
git clone https://github.com/Nikitosina/KeyboardExtAX.git
cd KeyboardExtAX
python3 -m venv .venv
.venv/bin/python -m pip install -e .
source .venv/bin/activateEvery installation provides keyboard-ext-ax and keyboard-ext-ax-mcp. The MCP server can be started with either keyboard-ext-ax-mcp or keyboard-ext-ax mcp.
The bundled Xcode project is ready to use. XcodeGen is needed only when modifying keyboard_ext_ax/harness/project.yml.
Output
The default response is a compact, flat array:
{
"ok": true,
"snapshot_id": "9543E2AA-C4D9-4281-9645-7792F7953FE3",
"simulator_udid": "2D78332B-A3C3-4CEE-962B-99EF531FD201",
"extension_bundle_id": "com.example.keyboard.extension",
"pid": 12345,
"elapsed_ms": 103.4,
"round_trip_ms": 106.1,
"session_reused": true,
"total_ms": 171.8,
"elements": [
{
"ref": "0.0.3",
"type": "Key",
"identifier": "space",
"label": "Space",
"frame": {
"x": 112,
"y": 746,
"width": 178,
"height": 44
},
"center": {
"x": 201,
"y": 768
},
"tap_x": 201,
"tap_y": 768
}
]
}Element fields
Field | Meaning |
| Stable path within this snapshot only. Refresh it after layout changes. |
| XCTest accessibility element type. |
| Accessibility identifier, when provided by the extension. |
| Accessibility label, when provided by the extension. |
| Absolute logical frame in Simulator coordinates. |
| Exact floating-point frame center. |
| Rounded integer center, ready for a gesture tool. |
Coordinates are Simulator logical coordinates, not screenshot pixels. Do not rescale them before passing them to XcodeBuildMCP for the same simulator.
Session management
keyboard-ext-ax status
keyboard-ext-ax status --simulator <SIMULATOR_UDID>
keyboard-ext-ax stop --simulator <SIMULATOR_UDID>
keyboard-ext-ax stop --allOne runner is maintained per simulator. Multiple simulators can be queried concurrently. If a reused runner loses the extension process, KeyboardExtAX recycles it once and returns client_refocus_required.
Errors
Errors are JSON objects with stable codes.
Code | Meaning | Recovery |
| A new or restarted runner is ready. | Refocus the client text field and repeat the request. |
| XCTest could not attach after three attempts. | Confirm that the requested extension—not the system keyboard—is visible, then retry. |
| The persistent XCTest runner did not become ready. | Inspect the returned |
| The XCTest harness failed to build. | Inspect the returned build log and verify the selected Xcode. |
| Xcode could not be located or queried. | Set |
| The runner received no extension identifier. | Supply |
| XCTest raised an unexpected snapshot error. | Retry with |
The CLI exits with:
0for success;2for a normal snapshot-state error such as inactive extension;1for lifecycle, build, or controller failures.
Architecture
MCP client / CLI
│
▼
KeyboardExtAXController
├─ build cache
├─ per-simulator lock and state
├─ free-port allocation
└─ XCTest runner lifecycle
│ JSON over loopback TCP
▼
PersistentKeyboardExtAXTests
│ XCUIApplication(bundleIdentifier:)
▼
Active keyboard extension process
│
▼
Parsed tree → compact elements → MCP/CLI JSONThe host app included in this repository is XCTest scaffolding. Snapshot mode does not launch it and does not replace the consumer's foreground client app.
License
KeyboardExtAX is available under the MIT License.
Available Tools
1 toolkeyboard_snapshotA
Return the active keyboard extension accessibility tree and tap coordinates.
The client app must already be foreground with a focused text field presenting the requested extension. The first call builds and starts cached XCTest support; later calls reuse the simulator session.
| Name | Required | Description | Default |
|---|---|---|---|
| raw | No | ||
| simulator_id | Yes | ||
| extension_bundle_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral disclosure burden. It does this well by noting that the first call builds and starts cached XCTest support while later calls reuse the simulator session, alerting the agent to an initialization cost and stateful behavior. It does not go into failure modes or permissions, but the core behavioral traits are disclosed.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two short, purposeful sentences. The primary purpose is front-loaded, and the second sentence supplies only essential usage and behavioral context. No filler or redundant restatement of the tool name appears.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description provides the key prerequisite, the first-call initialization behavior, and the output type, with an output schema covering return value structure. The main gap is the lack of parameter documentation, but the parameter names and output schema give an agent enough to invoke the tool correctly in most cases.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate, but it never mentions any of the three parameters. In particular, the 'raw' boolean flag has no explanation, and while simulator_id and extension_bundle_id are somewhat self-explanatory, their exact expected formats or relationship to the tool's behavior are left unspecified.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'Return the active keyboard extension accessibility tree and tap coordinates.' This clearly distinguishes the tool's purpose from a generic snapshot or screenshot utility, even without sibling tools to compare against.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives an explicit precondition: the client app must already be foreground with a focused text field presenting the requested extension. It does not name alternatives or exclusions, but with no sibling tools listed, this level of context is sufficient for an agent to know when calling is appropriate.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
v0.1.2- First observed
keyboard_snapshot
TDQS
Scored across 1 tool
With only one tool, there is no possibility of confusing it with another. The tool's purpose is clearly stated in its name and description.
The single tool name 'keyboard_snapshot' follows a clear noun_verb style and is descriptive. However, with only one tool there is no broader pattern to evaluate for consistency.
A single tool for a keyboard extension accessibility server feels too thin for the apparent scope. Typical interaction would require additional actions like typing, tapping, or dismissing the keyboard.
The server provides only a snapshot/read operation with no corresponding actions or lifecycle coverage. Significant gaps exist for any real keyboard extension testing workflow.
Maintenance
Related MCP Connectors
Drive real devices from your AI Coding tool. Embed a client SDK (Unity, Godot, Flutter, iOS/macOS, Android, React Native, Web) in your app, then capture screenshots, traverse the UI tree, inject taps and key events, and run automated test tasks on the physical device over a secure relay.
Clean PNG/JPEG screenshots via REST or MCP, with goal-driven multi-step navigation.
Build, run, and inspect iOS apps in disposable hosted Simulators from cloud coding agents.
remote debug iOS/Android/Unity/Godot/Flutter/RN/Web on real-device.ui-tree/screenshots/taps,tests.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to inspect and verify mobile UI hierarchies, nodes, styles, screenshots, and runtime attributes of iOS apps in simulators or on USB devices.261 npm2Apache 2.0
- AlicenseNot gradedqualityCmaintenanceMCP server that drives the iOS Simulator via accessibility-tree snapshots with stable element refs, enabling LLMs to interactively find, tap, type, and inspect UI elements at sub-200ms latency.22 npmMIT
- AlicenseAqualityBmaintenanceEnables driving an iOS Simulator from a model: taking screenshots, reading the UI tree, tapping, swiping, typing, managing app lifecycle and device state such as appearance, permissions, location, and notifications, without code signing or a physical device.216 npmMIT
- FlicenseNot gradedqualityCmaintenanceEnables macOS GUI automation via screenshots, mouse/keyboard control, accessibility tree inspection, and app/window management.-