pebble-mcp
An MCP server for the Pebble smartwatch ecosystem that exposes appstore search, a 64-color design toolkit, and the local dev loop (build → emulator → screenshot flows) as typed tools for any MCP host.
Capability probe (
capabilities): reports which tiers are live (appstore, design, devloop, auth) plus server version.Appstore (Tier 1, always on): search the store client-side (
store_search), fetch full app metadata (store_app), list collections/categories/developers (store_collection,store_category,store_developer), compare apps side-by-side (store_compare), and download.pbwfiles safely (store_download_pbw).Design toolkit (Tier 2, Pillow): find the nearest of 64 GColors (
color_nearest), render labeled swatch grids (palette_swatch), quantize images to the Pebble palette (image_quantize), resize/letterbox to platform targets (image_prep), recommend system fonts for a role or literal text (font_plan), and convert SVG vectors to.pdcfiles (pdc_convert).Dev loop (Tier 3, requires
pebbleCLI): build projects (pebble_build), install apps to the emulator (pebble_install), start/stop the emulator (emu_start,emu_stop), capture screenshots (emu_screenshot), send button/tap input (emu_input), stream bounded logs (emu_logs), and validate or run gallery-style flow specs returning every screenshot (flow_validate,flow_run).Project scaffolding & management: create new buildable C or Alloy projects (
project_new), inspect existing projects (project_info), add/register resources with optional design prep (project_add_resource), and edit metadata like UUID, platforms, capabilities, message keys (project_set_meta).Quality checks: heuristic design critique of a screenshot against the palette/contrast system (
design_review) and a one-call build-install-screenshot smoke test (project_smoke_test).Reference resources: read-only docs at
pebble://colors,pebble://fonts,pebble://platforms,pebble://wire-conventions, andpebble://touch-interaction.Authenticated (Tier 4, opt-in via
PEBBLE_API_TOKEN): reserved forpebble_publishand timeline pins.
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., "@pebble-mcp search the Pebble appstore for a minimal watchface"
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.
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-mcpThen, 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
pebbleCLI onPATH):pebble_build,pebble_install,emu_start,emu_stop,emu_screenshot,emu_input,emu_logs,flow_validate, and the crown jewelflow_run— run a gallery-style flow spec and get every screenshot back as MCP images. These tools register only whenpebbleis found.Tier 4 — Authenticated (requires
PEBBLE_API_TOKEN; off by default): reserved for token-gated, network-mutating operations —pebble_publishand timeline pins, and nothing else. Detected bycapabilities(); 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 # lintThe 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 toolscapabilitiesA
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
store_searchA
Search the store by keyword — CLIENT-SIDE, with real limits.
The live appstore API has NO search endpoint, so this does not do a
true full-text search. Instead it fetches a bounded candidate pool
from the relevant store listings (the all and most-loved
collections for type_string, paged up to a few hundred apps) and
ranks them by case-insensitive substring/token matches on each app's
title, author, and description. Results carry a score.
Consequences to keep in mind:
Coverage is only what those listings return: an obscure app outside the
all/most-lovedpool can be missed entirely.It matches on stored listing text, not on-device behavior or tags.
type_stringmust be one ofapps,watchapps-and-companions,faces,watchfaces. For an exact app you already know, preferstore_appby id.
Inputs: query (required), hardware, max_results, type_string. Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 29 tools
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.
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.
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.
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
Related MCP Connectors
A MCP server built for developers enabling Git based project management with project and personal…
Market data tools for the MCP ecosystem: server stats, events, search, leaderboards, package trends.
Hosted iOS app intelligence and creative research MCP server.
The official Planning Center MCP server for interacting with your ministry's data.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceA unified MCP server with composable tools for GitHub operations, file management, shell execution, kanban boards, Discord messaging, and package management. Features role-based security, HTTP/stdio transports, and a web-based development UI.GPL 3.0
- AlicenseNot gradedqualityDmaintenanceProfessional MCP Server with AST-powered code intelligence, high-performance search, and smart file operations.MIT
- FlicenseAqualityDmaintenanceAn MCP server providing tools for web research, code review, and concept explanation, callable by any MCP-compatible client.3-
- AlicenseAqualityDmaintenanceA comprehensive MCP server for MetaApp development, offering tools for project initialization, protocol exploration, and blockchain interaction.15MIT