Skip to main content
Glama
pebble-mcp
by dbonomo

pebble-mcp

An MCP server for the Pebble smartwatch ecosystem: appstore search, a 64-color design toolkit, and the local dev loop (build → emulator → screenshot flows) exposed as typed tools usable from any MCP host — Claude Code, Claude Desktop, claude.ai, or any other agent — not just a shell. The demo above is a single flow_run: it installs a real appstore app (Hubble) onto the emery emulator, drives the buttons, and captures each screen — all through MCP.

Install

Once published to PyPI, no checkout is needed — uvx runs it on demand. Add this to your MCP host's config (e.g. a Claude Code .mcp.json):

{
  "mcpServers": {
    "pebble-mcp": {
      "command": "uvx",
      "args": ["pebble-mcp"]
    }
  }
}

To run from a local checkout instead (development, or before the PyPI release):

{
  "mcpServers": {
    "pebble-mcp": {
      "command": "uv",
      "args": ["run", "--directory", "/absolute/path/to/pebble-mcp", "pebble-mcp"]
    }
  }
}

Quickstart (Claude Code)

# from PyPI (once published)
claude mcp add pebble-mcp -- uvx pebble-mcp

# or from a local checkout
claude mcp add pebble-mcp -- uv run --directory /absolute/path/to/pebble-mcp pebble-mcp

Then, in a session, ask the agent to call capabilities() to see which tiers are live, or store_search("weather") to hit the appstore immediately.

Related MCP server: Code Contractor MCP Server

Capability tiers

The server registers tools in four tiers and probes the environment at startup, so an agent only ever sees the tools it can actually run.

  • Tier 1 — Appstore (pure HTTPS, no auth, always on): store_search, store_app, store_collection, store_category, store_developer, store_compare, store_download_pbw.

  • Tier 2 — Design toolkit (pure Python + Pillow): color_nearest, palette_swatch, image_quantize, image_prep, font_plan, pdc_convert. Pillow is a core dependency, so this tier is normally always on; capabilities() still probes for it and degrades gracefully if it is somehow absent.

  • Tier 3 — Dev loop (requires the pebble CLI on PATH): pebble_build, pebble_install, emu_start, emu_stop, emu_screenshot, emu_input, emu_logs, flow_validate, and the crown jewel flow_run — run a gallery-style flow spec and get every screenshot back as MCP images. These tools register only when pebble is found.

  • Tier 4 — Authenticated (requires PEBBLE_API_TOKEN; off by default): reserved for token-gated, network-mutating operations — pebble_publish and timeline pins, and nothing else. Detected by capabilities(); opt-in only.

Read-only reference is also exposed as MCP resources: pebble://colors, pebble://fonts, pebble://platforms, pebble://wire-conventions, pebble://touch-interaction (swipe/tap handling on emery + gabbro, and how touch coexists with the buttons).

The capabilities() tier probe

Call the capabilities() tool first. It reports which tiers are live in the running environment (tier1_appstore, tier2_design, tier3_devloop, tier4_auth) so an agent can plan its work instead of calling a tool that isn't wired up. Tier 1 is always true; the rest reflect Pillow, the pebble CLI, and PEBBLE_API_TOKEN respectively.

Development

uv sync            # install deps (+ dev group: pytest, ruff)
uv run pytest      # run the test suite
uv run ruff check  # lint

The pebble_mcp/examples/flows/ directory holds sample flow specs (used by the tests and as living documentation of the flow format, including the # SAFETY RULES (live-write hazards — DO NOT TRIGGER): convention that flow authors should follow for any app with server-mutating screens).

Changelog

Release notes live in CHANGELOG.md.

License

MIT — see LICENSE. Copyright (c) 2026 Daniel Bonomo.


Not affiliated with, endorsed by, or sponsored by Core Devices or Rebble. "Pebble" and related marks belong to their respective owners.

Available Tools

29 tools
capabilitiesA

Report which pebble-mcp capability tiers are available in this environment.

tier1_appstore is always true (pure HTTPS). tier2_design requires Pillow. tier3_devloop requires the pebble CLI on PATH. tier4_auth requires PEBBLE_API_TOKEN to be set. Also reports the server version.

Inputs: no named parameters. Returns: object

color_nearestA

Nearest of the 64 Pebble GColors to an arbitrary color.

Args: color: a hex string ("#RRGGBB" / "RRGGBB" / shorthand) or an "r,g,b" triple. background: optional background color; when given, the contrast ratio and legibility of the matched color on it are also reported.

Returns the matched GColor name, hex, C constant, packed ARGB8 byte, sunlight-corrected display hex, and sRGB distance to the input.

Inputs: color (required), background. Returns: object

design_reviewA

Critique a Pebble screenshot against the design system.

Analyzes one screenshot and returns an actionable, compact critique: palette adherence (what fraction of pixels are exact GColors, and the most-common off-palette colors with their nearest GColor), per-region contrast/legibility judged against the sunlight-corrected display values (the reflective LCD reads duller than sRGB), role guidance vs the pebble://colors house roles, and a prioritized suggestion list.

source is a filesystem path, base64, or a data: URI of the image — OR the literal 'emulator' to capture and analyze the running emery screen (needs the pebble CLI; see capabilities()). regions sets the N×N contrast grid (default 3).

This is a heuristic, not a human designer: it reasons about color statistics, never glyphs — it flags pairs that look illegible and colors that are off-palette or role-inappropriate; confirm the hero-value hierarchy by eye.

Returns a JSON critique block plus the analyzed image.

Inputs: source (required), regions.

emu_inputA

Send one input to the running emulator.

action is 'press', 'longpress', or 'tap'. 'press'/'longpress' require button (one of back/up/select/down); 'longpress' also requires duration_ms (> 0). 'tap' fires an accelerometer tap and takes no button or duration. Button and action names are validated.

Inputs: action (required), button, duration_ms. Returns: object

emu_logsA

Capture emulator logs, bounded — never a firehose.

Streams pebble logs for at most seconds, optionally cutting at the first line containing until_pattern (matched), and clamps the result to the last max_bytes bytes (truncated).

Inputs: seconds, max_bytes, until_pattern. Returns: object

emu_screenshotA

Capture one screenshot of the running emery emulator as an image.

Optional name sets the file basename (.png appended if absent). Errors clearly if no emulator is running or the emulator is wedged.

Inputs: name.

emu_startA

Boot the Pebble emulator for the given platform (default emery).

Idempotent-ish: if one is already running this reconnects/restarts. Requires the pebble CLI on PATH.

Inputs: platform. Returns: object

emu_stopA

Shut down the running emulator; wipe=True also clears its storage.

Wiping is the fix for most emulator flakiness (apps not launching, stale state). Requires the pebble CLI on PATH.

Inputs: wipe. Returns: object

flow_runA

Run a gallery-style flow spec against the emery emulator and return every screenshot.

Parses flow_text (see pebble_mcp.flow for the line format), installs each app/pbw onto the emulator with the kill+wipe-first discipline, drives the buttons/waits, and captures the shot steps. Project dirs in app steps resolve against the repo root (parent of the pebble-mcp package dir, overridable via PEBBLE_MCP_REPO_ROOT). out_dir defaults to a fresh tempdir.

Returns a JSON metadata block — flow name, out_dir, per-shot {app, name, path, duration_s}, plus retries and wedge_recoveries — followed by one MCP image per shot.

Refuses any flow requesting more than 40 shots (runaway guard).

SAFETY — live-write hazard. flow_run drives REAL apps that can fire REAL network POSTs. A flow against an app with server-mutating screens must NEVER confirm a live-send action (e.g. press select on a "submit" / "send" screen or inside a value picker that commits on confirm). The convention is a # SAFETY RULES (live-write hazards — DO NOT TRIGGER): comment block at the top of every flow file; the bundled example flows under pebble_mcp/examples/flows/ show the pattern — follow their rules. Capturing those screens is fine; confirming them is not.

Inputs: flow_text (required), out_dir.

flow_validateA

Parse a flow spec WITHOUT running it — a cheap pre-check.

Returns a step summary (step_count, shot_count, steps_by_type, and whether it exceeds the 40-shot flow_run cap) on success, or a clear parse error naming the offending 1-based line. Needs no emulator, so it is the right tool to validate a flow before spending an emulator run.

Inputs: flow_text (required). Returns: object

font_planA

Recommend Pebble system fonts for a UI role or a literal string.

Pass a role like "hero numerals", "body label", "title", "timer", "clock face numerals", "hint", or "button label" (curated from this project's DESIGN.md type scale) to get an ordered list of FONT_KEY_* candidates -- no resource cost, they're baked into firmware.

Pass literal text instead (e.g. "15:37" or "Rest 90s") to check it against every system font's glyph coverage: numbers-only fonts (the LECO family, and the Bitham/Roboto "_NUMBERS"/"_SUBSET" variants) that can't render the text are flagged with fits: false and missing_glyphs instead of being silently recommended. Either way, the response includes minimal_character_regex -- the tight characterRegex for a custom TTF font that ships only the glyphs actually used (the classic "[0-9:]" clock-digit trick from developer.repebble.com/guides/app-resources/fonts/).

size_hint (pixels) sorts candidates by closeness to that size. style filters/prefers a weight substring, e.g. "bold".

Zero-argument sharp edge: this never touches the network or a toolchain -- it's pure lookup, always available regardless of capability tier.

Inputs: role_or_text (required), style, size_hint.

image_prepA

Resize/letterbox an image to a named target, quantize, and preview it.

Args: source: filesystem path, base64, or data: URI of the source image. target: "emery" (200×228), "menu-icon" (25×25), "appstore-banner" (720×320), or any platform name. fit: "contain" (letterbox) or "cover" (center-crop). dither: "floyd-steinberg" (default), "none", or "ordered". corrected: preview in sunlight-corrected display values.

Returns a stats object (target size, colors used, distance) plus the prepared preview image (2× nearest-neighbor upscale when small).

Inputs: source (required), fit, dither, target, +1 more.

image_quantizeA

Quantize an image to the Pebble 64-color palette; return stats + preview.

Args: source: a filesystem path, a base64 string, or a data: URI of the source image (hosts without a filesystem send base64). dither: "floyd-steinberg" (default), "none", or "ordered". corrected: preview in the sunlight-corrected display values instead of the bright uncorrected palette (reporting is unaffected).

Returns a stats object (colors used, per-pixel distance) plus the quantized preview image (2× nearest-neighbor upscale when small).

Inputs: source (required), dither, corrected.

palette_swatchA

Render a labeled grid of color swatches to a PNG for eyeballing.

Args: colors: hex strings and/or "r,g,b" triples (1..64 of them). labels: optional captions, one per color; defaults to each color's palette name (or its hex when off-palette).

Inputs: colors (required), labels.

pdc_convertA

Convert an SVG icon/vector to a Pebble Draw Command (.pdc) file.

Validates PDC's known constraints before attempting conversion, so a bad SVG comes back as a structured violation list -- never a broken or silently-wrong .pdc. Supported SVG elements: g, layer, path (straight-line commands M/L/H/V/Z only -- flatten curves first), rect, polyline, polygon, line, circle. Not supported: gradients, masks, clip paths, filters, embedded text/images, and any transform other than translate(x, y).

On success, returns the .pdc file as base64 (pdc_base64) plus size_bytes, image width/height, num_commands, and any non-fatal warnings (e.g. odd coordinates -- PDC recommends an even-integer coordinate grid for crisp rendering). On failure, valid is false, pdc_base64 is absent, and violations lists exactly what to fix, one entry per offending element/attribute.

Next move on failure: fix the listed elements/attributes in the SVG (flatten curves to polylines, remove gradients/masks, replace text with outlined paths) and call again -- this tool never guesses at a lossy conversion.

Inputs: svg_text (required).

pebble_buildA

Build a Pebble app project and return structured results.

Runs pebble build in project_dir (absolute, or relative to the workspace root). Returns success, the produced .pbw path, and compiler errors/warnings as structured file:line diagnostics. Requires the pebble CLI on PATH (see capabilities()).

Inputs: project_dir (required). Returns: object

pebble_installA

Install an app into the emulator, starting it if needed.

target is either a project directory (built first if needed) or a path to a prebuilt .pbw file; relative paths resolve against the workspace root. The emulator is killed and wiped first — the reliable-launch lesson — so expect a few seconds of startup. Requires the pebble CLI on PATH.

Inputs: target (required), platform. Returns: object

project_add_resourceA

Add a resource to a project and register it in package.json.

Copies source into <dir>/resources/ (created if missing) and appends/updates a resources.media entry with the given name and resource_type ("bitmap" | "font" | "raw"). For bitmaps, source may also be base64 / a data: URI, and passing prep_target (e.g. "emery", "menu-icon") routes the image through the design tier -- resize/letterbox + palette quantize -- before writing it. Returns the updated media list and the written file.

Inputs: dir (required), name (required), source (required), prep_target, +1 more.

project_infoA

Summarize an existing Pebble project from its package.json.

Returns name, displayName, uuid, kind/watchface, detected language (C vs JavaScript/Alloy, inferred from projectType and src/ layout), project_type, platforms, capabilities, messageKeys, resources (the media list), and sdkVersion. Raises a clear error if dir has no package.json or no pebble block.

Inputs: dir (required).

project_newA

Scaffold a new, immediately buildable Pebble project.

Writes the files directly (pure Python -- no pebble new-project shell-out), so it works on hosts without the CLI. The result compiles to a .pbw with pebble build as-is.

kind -- "watchface" (default; sets watchapp.watchface) or "watchapp". language -- "c" (default) emits src/c/<name>.c (a working digital-clock TextLayer for a watchface, or a Window+TextLayer for a watchapp) + wscript; "javascript" emits an Alloy project (projectType: "moddable", the modern on-watch JS engine): src/c/mdbl.c glue + src/embeddedjs/main.js

  • manifest.json + src/pkjs/index.js.

platforms -- defaults to all seven for C (aplite/basalt/chalk/diorite/emery/flint/gabbro). Alloy runs only on the Moddable-XS platforms emery (Time 2) and gabbro (Round 2), so JavaScript projects default to and are clamped to those (dropped platforms are reported in warnings).

companion adds a phone-side src/pkjs/index.js (implied for Alloy). config adds a pebble-clay settings page (src/pkjs/config.js + the dependency + companion). dest_dir is the parent directory; the project is created in <dest_dir>/<slug(name)>.

Returns path, uuid (a fresh uuid4), kind, language, platforms, files_created, warnings, and next_steps.

Inputs: name (required), kind, config, dest_dir, +3 more.

project_set_metaA

Edit a project's pebble metadata block in place.

uuid sets a specific UUID (or the literal "new" to generate a fresh uuid4). add_platforms / remove_platforms adjust targetPlatforms (validated against the seven known platforms, kept in canonical order). capabilities and message_keys replace those lists wholesale. Returns the updated pebble block.

Inputs: dir (required), uuid, capabilities, message_keys, +2 more.

project_smoke_testA

Build, install, and screenshot a project — the one-call "does it run?".

Builds dir (relative to the workspace root, or absolute); if it builds, installs the fresh .pbw onto the emulator (kill+wipe-first) and captures the launch screen. Returns {built, installed, errors[], warnings[], pbw_path, launch_shot, notes} plus the launch screenshot as an image.

A failed build returns built=false with the compiler errors and skips install — so this is also the fastest "did my change compile" check. Requires the pebble CLI on PATH (see capabilities()).

Inputs: dir (required), platform.

store_appA

Full metadata for one appstore app or watchface by its id.

Returns hearts, author, description, the list of compatible hardware platforms (with a has_emery convenience flag), the latest release (version + date + notes), the direct .pbw download URL, and screenshot/header image URLs.

Inputs: app_id (required). Returns: object

store_categoryA

List apps in a store category (e.g. faces, games, tools), paginated, as compact id/title/type/author/hearts summaries with a compatible flag for the requested hardware. has_more indicates further pages.

Inputs: category_slug (required), sort, limit, offset, +1 more. Returns: object

store_collectionA

List a named store collection, paginated, as compact summaries.

slug examples: most-loved, all, recently-updated, or a collection slug from a home row. type_string must be one of apps, watchapps-and-companions, faces, watchfaces. Each result row is id/title/type/author/hearts plus a compatible flag for the requested hardware. has_more indicates further pages; raise offset by limit to page.

Inputs: slug (required), type_string (required), sort, limit, +2 more. Returns: object

store_compareA

Bulk-fetch several apps by id and return a side-by-side comparison table for competitive research.

rows has one entry per found app with: hearts, type, the list of compatible platforms, a has_emery flag, the latest release date, and the .pbw URL. Any ids that don't resolve come back in missing_ids rather than failing the whole call.

Inputs: app_ids (required). Returns: object

store_developerA

List every app published by one developer, paginated, as compact id/title/type/author/hearts summaries with a compatible flag for the requested hardware. has_more indicates further pages.

Inputs: developer_id (required), sort, limit, offset, +1 more. Returns: object

store_download_pbwA

Download an app's latest-release .pbw to dest_dir.

Creates dest_dir if needed, writes the file, and returns its path and byte size (plus the app title and version). The on-disk filename is derived from the (untrusted) appstore response but always sanitized to a single basename inside dest_dir — a hostile id/version cannot traverse out. Refuses with a clear error if: the app has no published .pbw; dest_dir exists as a non-directory; the body is empty (0 bytes); or the body exceeds a 32 MiB safety cap (real .pbw files are well under a megabyte). Sends the polite pebble-mcp User-Agent. This feeds the install/emulator flow — hand the returned path to a Tier 3 install tool.

Inputs: app_id (required), dest_dir (required). Returns: object

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.1/5.0

    Scored across 29 tools

    Disambiguation5/5

    Each tool has a clearly distinct purpose: store tools differentiate by id/collection/category/developer/search/compare/download, image tools separate quantization from prep, flow_run vs flow_validate distinguish execution from parsing, and emulator tools split start/stop/screenshot/input/logs. No two tools are interchangeable.

    Naming Consistency4/5

    Most tools follow a <domain>_<operation> snake_case pattern (store_*, emu_*, project_*, pebble_*), but 'capabilities' is a bare noun and 'color_nearest' inverts verb-object order. A few tools use nouns as operations (palette_swatch, font_plan), introducing minor inconsistency.

    Tool Count2/5

    29 tools exceeds the 25+ threshold for 'too many' per the calibration rubric. While the scope is broad, the store subdomain alone accounts for 7 tools, making the overall set feel heavy for an MCP server.

    Completeness5/5

    The tool surface covers the full Pebble development workflow: project scaffolding/config/resource management, build/install/smoke testing, emulator control, flow validation/execution, store browsing/download, image processing, and design review. No critical gaps for the intended purpose.

    Maintenance

    ActivityStale
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers