Skip to main content
Glama
keponer

Garmin Connect IQ Simulator MCP Server

by keponer

Garmin Connect IQ Simulator MCP Server

macOS Connect IQ Model Context Protocol TypeScript

A Model Context Protocol (MCP) server providing 100% deterministic automation, input synthesis, and visual inspection for the Garmin Connect IQ Simulator (ConnectIQ.app) on macOS.

Enables AI coding agents (Antigravity, Claude Code, Cursor) to build, run, test, tap, swipe, press hardware buttons, stream logs, and visually inspect Garmin watchfaces, data fields, and device apps.


Architecture

flowchart TB
    LLM["LLM Agent (Antigravity / Claude / Cursor)"]
    MCP["Garmin Emulator MCP Server (TypeScript / Node.js)"]

    subgraph Engines["Core MCP Subsystems"]
        Meta["Device Metadata Engine\n(Parses simulator.json, compiler.json, simulator.ini)"]
        SDK["SDK & Shell Runner\n(monkeyc, monkeydo, bin/shell TCP 1234)"]
        Bridge["macOS Native UI Bridge\n(Swift CLI: Quartz CGWindowList, CGEvent, AXUIElement)"]
        Img["Image Pipeline (sharp)\n(Retina normalization, watch screen crop, SVG debug overlay)"]
    end

    subgraph GarminEnv["Garmin Connect IQ Environment (macOS)"]
        SimApp["ConnectIQ.app (simulator process)\nBundle: com.garmin.connectiq.simulator"]
        SimFiles["~/Library/Application Support/Garmin/ConnectIQ/\nDevices/*/simulator.json & Sdks/*"]
    end

    LLM <-->|"MCP Protocol (stdio)"| MCP
    MCP --> Meta
    MCP --> SDK
    MCP --> Bridge
    MCP --> Img

    Meta -->|"Reads button & screen coordinates"| SimFiles
    SDK -->|"TCP :1234 (launch, push .prg, logs)"| SimApp
    Bridge -->|"CGEvent (buttons, taps, swipes)\nscreencapture -l <windowID>\nAXUIElement (menu bar)"| SimApp
    Bridge -->|"Raw Window PNG"| Img
    Img -->|"Cropped Watch Screen PNG (base64)"| MCP

Related MCP server: MCP Connect

Available MCP Tools

1. Lifecycle & Device Management

Tool

Parameters

Description

simulator_status

none

Returns running state, window ID, bounds, macOS Screen Recording and Accessibility permissions, TCP port 1234 status, and active device profile metadata.

launch_simulator

wait_ms?: number

Launches ConnectIQ.app from the active SDK and waits for TCP port 1234 to be open (default timeout 15000ms).

list_devices

query?: string, touch_only?: boolean

Lists all installed Garmin device profiles in Devices/ with display resolution, touch capability, shape, and buttons.

set_active_device

device_id: string

Sets the active device ID (e.g. fenix847mm, venu3, fr965) for subsequent simulator commands, button presses, and screenshot cropping.

build_app

project_dir: string, device_id?: string, jungle_file?: string, developer_key_path?: string, output_prg_path?: string, extra_args?: string[]

Compiles a Monkey C project for a specified device using the SDK monkeyc compiler.

run_app

prg_path: string, device_id?: string, wait_ms?: number, capture_after?: boolean

Pushes and executes a compiled .prg binary on the active device using monkeydo, streaming logs and optionally returning an initial screenshot.

stop_app

none

Terminates the active app child process in the simulator.

run_unit_tests

prg_path: string, device_id?: string, test_name?: string, timeout_ms?: number

Runs unit tests on a compiled test .prg using monkeydo -t.

2. Navigation & User Input Synthesis

Tool

Parameters

Description

press_button

button: string, hold?: boolean, hold_ms?: number, device_id?: string, method?: "mouse" | "key", capture_after?: boolean, wait_ms?: number

Presses a physical bezel button (select, back, up, down, menu). Supports short click or long-press hold, returning the updated watch screen.

press_button_sequence

steps: Array<{ button, hold?, hold_ms?, wait_ms? }>, device_id?: string, capture_after?: boolean

Executes a sequence of button presses in order with delays between steps, returning the final watch screen.

send_key

key?: string, key_code?: number, hold_ms?: number, capture_after?: boolean, wait_ms?: number, device_id?: string

Sends a macOS virtual key code or named key (enter, esc, up, down, menu, space) to the simulator window.

tap_screen

x: number, y: number, normalized?: boolean, device_id?: string, capture_after?: boolean, wait_ms?: number

Clicks the watch touchscreen at display coordinates $(X, Y)$ or normalized $(0.0..1.0)$, returning the updated screen.

swipe_screen

direction: "up" | "down" | "left" | "right", device_id?: string, capture_after?: boolean, wait_ms?: number

Synthesizes a touch drag adhering to device gesture thresholds (e.g. right swipe triggers onBack), returning the updated screen.

hold_screen

x: number, y: number, duration_ms?: number, normalized?: boolean, device_id?: string, capture_after?: boolean, wait_ms?: number

Long-presses at specific watch display coordinates (default 1000ms), returning the updated screen.

3. Visual Inspection & Logs

Tool

Parameters

Description

get_screenshot

view?: "screen" | "bezel" | "annotated", device_id?: string, save_path?: string

Captures the simulator window. "screen" crops to watch display (1:1 pixel mapping); "bezel" captures the watch frame; "annotated" draws button hitboxes and a coordinate grid.

get_app_logs

lines?: number, clear?: boolean

Retrieves console output, System.println() logs, and crash stack traces from the running app.

4. Environment & Menus

Tool

Parameters

Description

trigger_menu_action

menu_path: string[], capture_after?: boolean, wait_ms?: number

Triggers a macOS menu item in ConnectIQ.app via Accessibility (e.g. ["Simulation", "Activity Data"]).

set_glance_mode

enabled?: boolean, capture_after?: boolean, wait_ms?: number

Toggles or sets Glance View mode (Settings > Glance View) for testing widget glances.


Coordinate Calibration & Retina Scaling

  1. Watch Screen Crop (view: "screen"): Garmin devices have exact display bounding boxes specified in Devices/<device_id>/simulator.json (e.g. 454x454 at offset (134, 221) for fenix847mm). On macOS Retina displays ($2\times$ pixel density), the server scales the crop window dynamically and normalizes the output image back to $1\times$ logical device pixels (454x454), saving up to 75% of vision tokens while matching code drawing coordinates 1:1.

  2. macOS Title & Status Bar Calibration: In ConnectIQ.app (wxWidgets on macOS), the window features a 28px macOS title bar at the top and a 28px status bar at the bottom. The client canvas origin is computed as: $$\text{originX} = \max\left(0, \left\lfloor \frac{\text{windowWidth} - \text{bezelWidth}}{2} \right\rfloor\right), \quad \text{originY} = 28$$ This ensures mouse clicks and drag gestures align with sub-pixel accuracy to hardware bezel buttons and touchscreen boundaries.


Required macOS Permissions

The terminal or IDE hosting the MCP server requires two standard macOS permissions:

  1. Screen Recording:

    • Required by /usr/sbin/screencapture to capture simulator window buffers.

    • Prompted automatically on first screenshot or grant via: System Settings > Privacy & Security > Screen & System Audio Recording

  2. Accessibility:

    • Required by CGEvent to synthesize mouse clicks, drags, and keystrokes, and by AXUIElement to trigger menu items.

    • Grant via: System Settings > Privacy & Security > Accessibility

Run simulator_status at any time to verify permission status.


Installation & Setup

1. Build from Source

Ensure Xcode Command Line Tools (swiftc) and Node.js $\ge 18$ are installed:

git clone https://github.com/keponer/garmin-simulator-mcp.git
cd garmin-simulator-mcp
npm install
npm run build

This compiles:

  • bin/macos-bridge (native Swift CLI helper)

  • dist/index.js (TypeScript MCP server)

Run all tests:

npm test

Client Configuration

Claude Desktop (claude_desktop_config.json)

Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "garmin-emulator": {
      "command": "node",
      "args": [
        "/path/to/garmin-emulator-mcp/dist/index.js"
      ]
    }
  }
}

Cursor (.cursor/mcp.json)

Add to .cursor/mcp.json or ~/.cursor/mcp.json:

{
  "mcpServers": {
    "garmin-emulator": {
      "command": "node",
      "args": [
        "/path/to/garmin-emulator-mcp/dist/index.js"
      ]
    }
  }
}

Antigravity (~/.gemini/config/mcp_config.json)

Add to ~/.gemini/config/mcp_config.json:

{
  "mcpServers": {
    "garmin-emulator": {
      "command": "node",
      "args": [
        "/path/to/garmin-emulator-mcp/dist/index.js"
      ]
    }
  }
}

License

MIT

Related MCP Connectors

Related MCP Servers