pebble-mcp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. pebble_mcp-0.1.0/.gitignore +5 -0
  2. pebble_mcp-0.1.0/EMU-TESTING-SPEC.md +84 -0
  3. pebble_mcp-0.1.0/LAUNCH.md +171 -0
  4. pebble_mcp-0.1.0/LICENSE +21 -0
  5. pebble_mcp-0.1.0/PKG-INFO +133 -0
  6. pebble_mcp-0.1.0/README.md +110 -0
  7. pebble_mcp-0.1.0/TOOL-SURFACE.md +98 -0
  8. pebble_mcp-0.1.0/USABILITY-FINDINGS.md +73 -0
  9. pebble_mcp-0.1.0/assets/palettes/pebble_colors_64.act +0 -0
  10. pebble_mcp-0.1.0/assets/palettes/pebble_colors_64.gif +0 -0
  11. pebble_mcp-0.1.0/assets/palettes/pebble_colors_64.pal +0 -0
  12. pebble_mcp-0.1.0/assets/palettes/pebble_colors_sunlight.aseprite +0 -0
  13. pebble_mcp-0.1.0/assets/palettes/pebble_colors_uncorrected.aseprite +0 -0
  14. pebble_mcp-0.1.0/community.html +445 -0
  15. pebble_mcp-0.1.0/docs/demo.gif +0 -0
  16. pebble_mcp-0.1.0/pebble-mcp-requirements.md +130 -0
  17. pebble_mcp-0.1.0/pebble_mcp/__init__.py +3 -0
  18. pebble_mcp-0.1.0/pebble_mcp/capabilities.py +52 -0
  19. pebble_mcp-0.1.0/pebble_mcp/devloop.py +449 -0
  20. pebble_mcp-0.1.0/pebble_mcp/examples/flows/coach-logger.flow +27 -0
  21. pebble_mcp-0.1.0/pebble_mcp/examples/flows/coach-watchface.flow +8 -0
  22. pebble_mcp-0.1.0/pebble_mcp/examples/flows/coach-workout.flow +43 -0
  23. pebble_mcp-0.1.0/pebble_mcp/examples/flows/hail-watch.flow +13 -0
  24. pebble_mcp-0.1.0/pebble_mcp/flow.py +569 -0
  25. pebble_mcp-0.1.0/pebble_mcp/fonts.py +385 -0
  26. pebble_mcp-0.1.0/pebble_mcp/images.py +512 -0
  27. pebble_mcp-0.1.0/pebble_mcp/palette.py +443 -0
  28. pebble_mcp-0.1.0/pebble_mcp/pdc.py +653 -0
  29. pebble_mcp-0.1.0/pebble_mcp/platforms.py +50 -0
  30. pebble_mcp-0.1.0/pebble_mcp/resources.py +272 -0
  31. pebble_mcp-0.1.0/pebble_mcp/server.py +50 -0
  32. pebble_mcp-0.1.0/pebble_mcp/store.py +637 -0
  33. pebble_mcp-0.1.0/pebble_mcp/tools_design.py +207 -0
  34. pebble_mcp-0.1.0/pebble_mcp/tools_devloop.py +86 -0
  35. pebble_mcp-0.1.0/pebble_mcp/tools_flow.py +391 -0
  36. pebble_mcp-0.1.0/pebble_mcp/tools_fonts.py +94 -0
  37. pebble_mcp-0.1.0/pebble_mcp/tools_store.py +491 -0
  38. pebble_mcp-0.1.0/pyproject.toml +72 -0
  39. pebble_mcp-0.1.0/roadmap.md +81 -0
  40. pebble_mcp-0.1.0/scripts/gen_palette.py +110 -0
  41. pebble_mcp-0.1.0/tests/__init__.py +0 -0
  42. pebble_mcp-0.1.0/tests/fixtures/store/app_by_id_hubble.json +217 -0
  43. pebble_mcp-0.1.0/tests/fixtures/store/app_not_found.json +1 -0
  44. pebble_mcp-0.1.0/tests/fixtures/store/apps_bulk.json +337 -0
  45. pebble_mcp-0.1.0/tests/fixtures/store/apps_bulk_missing.json +1 -0
  46. pebble_mcp-0.1.0/tests/fixtures/store/category_faces.json +461 -0
  47. pebble_mcp-0.1.0/tests/fixtures/store/collection_all_apps.json +342 -0
  48. pebble_mcp-0.1.0/tests/fixtures/store/collection_most_loved_watchfaces.json +505 -0
  49. pebble_mcp-0.1.0/tests/fixtures/store/collection_not_found.json +1 -0
  50. pebble_mcp-0.1.0/tests/fixtures/store/developer_apps.json +512 -0
  51. pebble_mcp-0.1.0/tests/fixtures/store/invalid_app_type.json +1 -0
  52. pebble_mcp-0.1.0/tests/test_capabilities.py +59 -0
  53. pebble_mcp-0.1.0/tests/test_degradation.py +232 -0
  54. pebble_mcp-0.1.0/tests/test_devloop.py +389 -0
  55. pebble_mcp-0.1.0/tests/test_flow.py +468 -0
  56. pebble_mcp-0.1.0/tests/test_fonts.py +275 -0
  57. pebble_mcp-0.1.0/tests/test_images.py +337 -0
  58. pebble_mcp-0.1.0/tests/test_palette.py +355 -0
  59. pebble_mcp-0.1.0/tests/test_pdc.py +313 -0
  60. pebble_mcp-0.1.0/tests/test_platforms.py +34 -0
  61. pebble_mcp-0.1.0/tests/test_resources.py +126 -0
  62. pebble_mcp-0.1.0/tests/test_server.py +47 -0
  63. pebble_mcp-0.1.0/tests/test_store.py +442 -0
  64. pebble_mcp-0.1.0/tests/test_tools_design.py +185 -0
  65. pebble_mcp-0.1.0/tests/test_tools_devloop.py +60 -0
  66. pebble_mcp-0.1.0/tests/test_tools_flow.py +326 -0
  67. pebble_mcp-0.1.0/tests/test_tools_fonts.py +89 -0
  68. pebble_mcp-0.1.0/tests/test_tools_store.py +491 -0
  69. pebble_mcp-0.1.0/uv.lock +781 -0
@@ -0,0 +1,5 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .pytest_cache/
4
+ .ruff_cache/
5
+ .venv/
@@ -0,0 +1,84 @@
1
+ # pebble-mcp — Emulator boot / manage / test-app tool spec
2
+
3
+ What it takes to launch and drive a Pebble app in the emery emulator, distilled
4
+ from doing it by hand across a full app build. This is the contract for the
5
+ Tier-3 dev-loop tools that let an agent boot the emulator, install apps, drive
6
+ them, and see the result. Honors TOOL-SURFACE.md.
7
+
8
+ > **STATUS — implemented (2026-08-01).** The wiring "gap" this spec describes is
9
+ > closed: all Tier-3 tools listed below are registered on the server
10
+ > (`pebble_build`, `pebble_install`, `emu_start`, `emu_stop`, `emu_screenshot`,
11
+ > `emu_input`, `emu_logs`, `flow_run`, `flow_validate`). The "Operational
12
+ > knowledge to bake in" folklore is now infrastructure: button-press pacing
13
+ > (`BUTTON_SETTLE_SEC`) and post-install settle (`POST_INSTALL_SETTLE_SEC`) are
14
+ > baked into the flow driver and `emu_input`, so correctness no longer depends
15
+ > on a flow author inserting waits. Kept as the design record. The two items
16
+ > still deliberately unimplemented — auto kill+wipe recovery on *standalone* emu
17
+ > tools (they surface a clear hint instead, to avoid destroying state the agent
18
+ > may want), and hard serialization of the single emulator — are noted inline.
19
+
20
+ ## Current state (what exists vs. the gap)
21
+ - `devloop.py` LIBRARY already has: `build()`, `install()` (kill+wipe-first),
22
+ `emu_start()`, `emu_stop(wipe=)`, `logs_capture()`, plus GCC-diagnostic parsing
23
+ and WEDGE_MARKERS.
24
+ - `flow.py` LIBRARY already has the full driver: parse a flow spec, install, and
25
+ step through button/tap/wait/screenshot with wedge recovery.
26
+ - MISSING in the library: `emu_screenshot()` and `emu_input()` (button/tap).
27
+ - MISSING on the server: NONE of these are registered as MCP tools yet — the
28
+ server only exposes `capabilities()` + `pebble://platforms`. Wiring is the gap.
29
+
30
+ ## The launch/test loop (what "launch an app" actually requires)
31
+ 1. **Gate** on `capabilities().tier3_devloop` (pebble CLI on PATH, ~/.local/bin).
32
+ If absent, fail fast with the install hint — never a traceback.
33
+ 2. **Build** (optional): `pebble build` in a project dir → `build/<name>.pbw`.
34
+ Return structured diagnostics (already implemented).
35
+ 3. **Clean install**: `pebble kill` → `pebble wipe` → sleep ~3s →
36
+ `pebble install --emulator emery <pbw>`. The kill+wipe BEFORE install is
37
+ load-bearing — a persisted active app otherwise stays foreground and the new
38
+ app never launches. (Already baked into `install()`.)
39
+ 4. **Settle**: sleep ~6s after install for the app to fully render (some apps
40
+ load resources / animate).
41
+ 5. **Observe**: screenshot the current screen (native 200x228 PNG).
42
+ 6. **Drive**: send button/tap input, screenshot again, repeat.
43
+
44
+ ## Tools to add / wire (small surface, per TOOL-SURFACE.md)
45
+ - `pebble_build(project_dir)` — wire `build()`. Structured errors.
46
+ - `pebble_install(pbw_or_project)` — wire `install()` (kill+wipe baked in).
47
+ - `emu_start()` / `emu_stop(wipe?)` — wire existing.
48
+ - **`emu_screenshot()`** — NEW. `pebble screenshot --no-open <tmp>`; verify the
49
+ file is non-empty; return as an **MCP image content block** (agent sees it)
50
+ PLUS the saved path. This is the key tool for "testing".
51
+ - **`emu_input(action, button?, duration_ms?)`** — NEW. Wraps
52
+ `pebble emu-button click <back|up|select|down>` (+ `--duration` for long-press)
53
+ and `pebble emu-tap`. Validate button against the enum.
54
+ - `emu_logs(...)` — wire `logs_capture()` (bounded).
55
+ - **`flow_run(flow_or_project)`** — wire `flow.py`'s `run_flow()`: install + step
56
+ a scripted flow, returning shots as image content + a contact sheet. This is
57
+ the one-call "test an app" payoff (roadmap C3).
58
+
59
+ ## Operational knowledge to BAKE IN (folklore → infrastructure)
60
+ - **Button-press pacing:** the emery emulator DROPS TO THE WATCHFACE / desyncs on
61
+ rapid presses. `emu_input` must pace internally (~0.2-0.3s between presses) and
62
+ flow_run must space steps — never fire presses back-to-back.
63
+ - **Wedge recovery:** output containing any WEDGE_MARKER ("not responding",
64
+ "no emulator", "failed to connect", "connection refused", "timed out") → the
65
+ emulator is wedged; run kill+wipe and retry the operation ONCE, then fail with
66
+ a clear message. (flow.py already does this; emu tools should too.)
67
+ - **Screenshot capture is racy for animations:** a sub-second animation can't be
68
+ reliably sampled through screenshot latency. For animated content, expose an
69
+ optional per-frame step (or document capturing a slowed build) rather than
70
+ promising to catch a transient.
71
+ - **Single emulator:** there is ONE QEMU instance. Serialize emulator-touching
72
+ tools (queue or fail clearly); never let two installs/screenshots interleave.
73
+ - **Window vs headless:** launching surfaces a QEMU window on a local display
74
+ when one is available (the user can interact directly), and still screenshots
75
+ fine when headless. Don't assume either.
76
+ - **Timings:** ~3s after wipe, ~6s after install, before the first screenshot.
77
+
78
+ ## Acceptance
79
+ A fresh agent with the server configured can, in ≤ a few calls:
80
+ 1. build a project and report an actionable compile error,
81
+ 2. install it and SEE the launch screen (image content),
82
+ 3. press a button and see the screen change,
83
+ 4. run a multi-step flow and get back the screenshots + contact sheet —
84
+ each degrading gracefully (named gate error) when the pebble CLI is absent.
@@ -0,0 +1,171 @@
1
+ # Launch checklist — get pebble-mcp on PyPI
2
+
3
+ Your follow-along working doc. The community page's TODO is the public
4
+ overview; **this** is the granular execution list. Check boxes as you go.
5
+
6
+ **Legend**
7
+ - 🟢 **Claude does it** — just say "go" and I'll do it (you review after).
8
+ - 🔵 **You do it** — needs your account, credentials, or a decision only you can make.
9
+ - ⏱ rough time.
10
+
11
+ **This weekend's goal: get fully publish-READY and kick the tires — not publish.**
12
+ Publishing (Steps 5–6) waits until you've tested enough to feel confident. So the
13
+ plan is: I make everything publish-ready *in place* (no repo carve-out yet, so your
14
+ testing environment stays intact), and you spend the weekend actually using it
15
+ (see "Kick the tires" below). Pull the trigger on PyPI whenever you're ready.
16
+
17
+ ---
18
+
19
+ ## Step 0 — Decisions · 🔵 · ✅ DONE
20
+
21
+ - [x] **License: MIT.**
22
+ - [x] **Package name: `pebble-mcp`** (confirmed available on PyPI).
23
+ - [x] **Authorship: Daniel Bonomo <d.z.bonomo@gmail.com>.**
24
+
25
+ ---
26
+
27
+ ## Kick the tires this weekend — get a feel for it · 🔵 · ⏱ as long as it's fun
28
+
29
+ Do this from your **second terminal** (the MCP-connected one). **Restart that
30
+ instance first** so it picks up all 23 tools — it connected when the server had
31
+ only one. Then just talk to Claude naturally; these are prompts to try, grouped by
32
+ tier. Jot down anything that feels wrong — that's exactly the feedback Phase 3 needs.
33
+
34
+ **Tier 1 · Store (works even with no emulator):**
35
+ - [ ] "Search the Pebble store for weather watchfaces that run on my Time 2."
36
+ - [ ] "Show me the most-loved watchfaces — which ones include weather?"
37
+ - [ ] "Compare Hubble against another astronomy app: hearts, platforms, size."
38
+ - [ ] "Download the Hubble pbw so we can try it."
39
+
40
+ **Tier 2 · Design (the fun, visual one — works anywhere, even claude.ai):**
41
+ - [ ] "What's the nearest Pebble color to #3b82f6? Is it legible on black?"
42
+ - [ ] "Quantize this image to the Pebble 64-color palette." *(drop in a logo/photo)*
43
+ - [ ] "Prep this image as a 25×25 launcher menu icon."
44
+ - [ ] "Make me a swatch of the coach app's accent colors."
45
+ - [ ] "Which font for a big clock numeral? Give me the characterRegex for `15:37`."
46
+ - [ ] "Convert this SVG to PDC." *(try one with a curve — watch it refuse honestly)*
47
+
48
+ **Tier 3 · Dev loop (uses your local `pebble` CLI):**
49
+ - [ ] "Run a screenshot flow on hail-watch and show me the frames."
50
+ - [ ] "Build coach-workout and tell me about any warnings."
51
+ - [ ] "Install Hubble in the emulator and screenshot the moon screen."
52
+ - [ ] "Screenshot whatever's on the emulator right now."
53
+
54
+ **Resources (reference data the model can pull):**
55
+ - [ ] "Read the pebble://colors resource — what are the role colors?"
56
+ - [ ] "Show me the wire-format conventions from pebble-mcp."
57
+
58
+ **Try to break it / find rough edges (this is the valuable part):**
59
+ - [ ] Give a vague request ("make my watchface art Pebble-ready") and see if Claude
60
+ picks the right tool from the descriptions alone.
61
+ - [ ] Feed junk: a corrupt image, a nonsense color, a 10,000-character search.
62
+ - [ ] Note **any** moment Claude picks the wrong tool, an error message confuses
63
+ you, or you expected a tool that doesn't exist. Add it to
64
+ `pebble-mcp/TOOL-SURFACE.md` (your other terminal already started that doc).
65
+
66
+ ---
67
+
68
+ ## Step 0-decisions are done. The rest below is the ready-then-publish path.
69
+
70
+ ---
71
+
72
+ ## Step 1 — Make it publish-ready in place (decouple from the monorepo) · 🟢 · IN PROGRESS
73
+
74
+ Doing this now, *without* carving out a separate repo yet — so your testing
75
+ environment stays a single tree all weekend. The physical carve-out + GitHub push
76
+ becomes a mechanical step at publish time (Step 5b), because after this the
77
+ package has no monorepo coupling left.
78
+
79
+ - [ ] 🟢 **Break the monorepo coupling:** `tests/test_flow.py` reads the flow
80
+ specs from `../tools/flows/`. Vendor those `.flow` files into the package
81
+ (`pebble_mcp/examples/flows/`) and repoint the test — verified by running
82
+ pytest on a copy of the package alone, outside the repo.
83
+ - [ ] 🟢 Genericize the last coach/hail mentions in `tools_flow.py` / `devloop.py`
84
+ docstrings (public repo hygiene; safety wording stays).
85
+ - [ ] 🟢 Confirm no personal leaks (`btf4e`, `dbonomo`, `/media/`, `trainer`).
86
+
87
+ ### Step 5b (deferred to publish day) — carve out & push · 🔵
88
+ - [ ] Create the empty **public** GitHub repo `pebble-mcp` (no README/license — we
89
+ bring our own). Copy the now-self-contained `pebble-mcp/` dir into it; I'll
90
+ give you the exact `git init` + `remote add` + `push`.
91
+
92
+ ---
93
+
94
+ ## Step 2 — Packaging metadata · 🟢 · ⏱ 20 min
95
+
96
+ - [ ] 🟢 Add the `LICENSE` file (once you've picked one in Step 0).
97
+ - [ ] 🟢 Fill out `pyproject.toml`: `description`, `authors`, `license`,
98
+ `keywords`, `classifiers` (Python 3.13, MCP, License, OS), `readme`,
99
+ `requires-python = ">=3.13"`, and `[project.urls]` (Homepage / Repository
100
+ / Issues).
101
+ - [ ] 🟢 Verify the build: `uv build` produces an sdist + wheel, and
102
+ `uvx --from ./dist/pebble_mcp-*.whl pebble-mcp` boots from the wheel alone.
103
+
104
+ ---
105
+
106
+ ## Step 3 — README · 🟢 · ⏱ 20 min
107
+
108
+ - [ ] 🟢 Write `README.md`: one-paragraph what-it-is, the four tiers, the `uvx`
109
+ config snippet, a quickstart per host (Claude Code / Desktop), the
110
+ `capabilities()` note, license, and the "not affiliated with Core
111
+ Devices/Rebble" line.
112
+ - [ ] 🟢 Embed the demo GIF from Step 4 at the top.
113
+
114
+ ---
115
+
116
+ ## Step 4 — The demo GIF · 🟢 (needs the local emulator) · ⏱ 20 min
117
+
118
+ - [ ] 🟢 Run the Hubble store→screenshot flow through `flow_run` and assemble the
119
+ captured frames into an animated GIF. **This is the single most convincing
120
+ launch artifact** — it's what earns the retweet.
121
+ - [ ] 🟢 Drop it in the repo (`docs/demo.gif`) and wire it into the README + the
122
+ community page.
123
+
124
+ ---
125
+
126
+ ## Step 5 — Release workflow, trusted publishing (no tokens) · 🟢 write / 🔵 configure · ⏱ 15 min
127
+
128
+ - [ ] 🟢 Add `.github/workflows/release.yml` — builds on a `v*` tag and publishes
129
+ via `pypa/gh-action-pypi-publish` with `permissions: id-token: write` (OIDC,
130
+ so **no API token to store**).
131
+ - [ ] 🔵 On PyPI: create an account if needed, verify email, **enable 2FA**.
132
+ - [ ] 🔵 On PyPI → your account → *Publishing* → **Add a pending publisher** with:
133
+ PyPI project name `pebble-mcp`, owner `<your-github-user>`, repository
134
+ `pebble-mcp`, workflow `release.yml`, environment (leave blank or `pypi`).
135
+ *(This authorizes GitHub to publish without a password.)*
136
+
137
+ ---
138
+
139
+ ## Step 6 — Ship it · 🔵 tag / 🟢 verify · ⏱ 10 min
140
+
141
+ - [ ] 🔵 `git tag v0.1.0 && git push origin v0.1.0` — this triggers the workflow,
142
+ which publishes to PyPI.
143
+ - [ ] 🟢 Verify the real thing: `uvx pebble-mcp` from a clean directory boots and
144
+ `capabilities()` responds. (I'll walk the install as a stranger would.)
145
+ - [ ] 🟢 Flip the community page + config snippets from "not yet on PyPI / local
146
+ checkout" to the live `uvx pebble-mcp` one-liner.
147
+
148
+ ---
149
+
150
+ ## Step 7 — Announce (in this order) · 🟢 draft / 🔵 post · ⏱ 30 min
151
+
152
+ - [ ] 🔵 **Rebble Showcase forum thread** (forum.rebble.io) — the durable home
153
+ base. 🟢 I'll draft it (lead with the SVG→PDC + 64-color quantization hook).
154
+ - [ ] 🔵 **X, tagging @ericmigi** — highest leverage; he's publicly wanted exactly
155
+ this. 🟢 I'll draft the post; the GIF is the payload.
156
+ - [ ] 🔵 **Discord `#app-dev`** (rebble.io/discord) — cross-post the forum thread.
157
+ 🟢 I'll draft the message.
158
+ - [ ] 🔵 **GitHub `pebble/community-resources` PR** — adds pebble-mcp to the
159
+ official tools listing (permanent, low-effort). 🟢 I'll prep the branch +
160
+ the markdown file; you submit the PR.
161
+ - [ ] Secondary / opportunistic: Show HN, r/pebble (check sidebar rules first),
162
+ and a demo slot if the monthly dev hangout lands near launch.
163
+
164
+ ---
165
+
166
+ ### Status
167
+ Step 0 decisions are locked. Steps 1–4 (decouple, license, packaging, README, demo
168
+ GIF) are being prepared **now, in place** — so by the time you're done testing,
169
+ publishing is just: create the public repo (5b), configure the PyPI trusted
170
+ publisher (5), and push a `v0.1.0` tag (6). No rush — pull that trigger only once
171
+ the weekend's testing has you confident.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Bonomo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.4
2
+ Name: pebble-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for the Pebble smartwatch ecosystem: appstore search, a 64-color design toolkit, and the local dev loop (build, emulator, screenshot flows).
5
+ Project-URL: Homepage, https://github.com/dzbonomo/pebble-mcp
6
+ Project-URL: Repository, https://github.com/dzbonomo/pebble-mcp
7
+ Project-URL: Issues, https://github.com/dzbonomo/pebble-mcp/issues
8
+ Author-email: Daniel Bonomo <d.z.bonomo@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: mcp,model-context-protocol,pebble,rebble,watchface
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Software Development
17
+ Requires-Python: >=3.13
18
+ Requires-Dist: mcp>=1.9.0
19
+ Requires-Dist: pillow>=10.0.0
20
+ Provides-Extra: design
21
+ Requires-Dist: pillow>=10.0.0; extra == 'design'
22
+ Description-Content-Type: text/markdown
23
+
24
+ # pebble-mcp
25
+
26
+ <p align="center">
27
+ <img src="docs/demo.gif" alt="pebble-mcp downloading a store app and screenshotting it in the emulator" width="200">
28
+ </p>
29
+
30
+ An [MCP](https://modelcontextprotocol.io) server for the Pebble smartwatch
31
+ ecosystem: appstore search, a 64-color design toolkit, and the local dev loop
32
+ (build → emulator → screenshot flows) exposed as typed tools usable from any
33
+ MCP host — Claude Code, Claude Desktop, claude.ai, or any other agent — not
34
+ just a shell. The demo above is a single `flow_run`: it installs a real
35
+ appstore app (Hubble) onto the emery emulator, drives the buttons, and captures
36
+ each screen — all through MCP.
37
+
38
+ ## Install
39
+
40
+ Once published to PyPI, no checkout is needed — `uvx` runs it on demand. Add
41
+ this to your MCP host's config (e.g. a Claude Code `.mcp.json`):
42
+
43
+ ```json
44
+ {
45
+ "mcpServers": {
46
+ "pebble-mcp": {
47
+ "command": "uvx",
48
+ "args": ["pebble-mcp"]
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ To run from a local checkout instead (development, or before the PyPI release):
55
+
56
+ ```json
57
+ {
58
+ "mcpServers": {
59
+ "pebble-mcp": {
60
+ "command": "uv",
61
+ "args": ["run", "--directory", "/absolute/path/to/pebble-mcp", "pebble-mcp"]
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ ### Quickstart (Claude Code)
68
+
69
+ ```bash
70
+ # from PyPI (once published)
71
+ claude mcp add pebble-mcp -- uvx pebble-mcp
72
+
73
+ # or from a local checkout
74
+ claude mcp add pebble-mcp -- uv run --directory /absolute/path/to/pebble-mcp pebble-mcp
75
+ ```
76
+
77
+ Then, in a session, ask the agent to call `capabilities()` to see which tiers
78
+ are live, or `store_search("weather")` to hit the appstore immediately.
79
+
80
+ ## Capability tiers
81
+
82
+ The server registers tools in four tiers and probes the environment at startup,
83
+ so an agent only ever sees the tools it can actually run.
84
+
85
+ - **Tier 1 — Appstore** (pure HTTPS, no auth, always on):
86
+ `store_search`, `store_app`, `store_collection`, `store_category`,
87
+ `store_developer`, `store_compare`, `store_download_pbw`.
88
+ - **Tier 2 — Design toolkit** (pure Python + Pillow):
89
+ `color_nearest`, `palette_swatch`, `image_quantize`, `image_prep`,
90
+ `font_plan`, `pdc_convert`. Pillow is a core dependency, so this tier is
91
+ normally always on; `capabilities()` still probes for it and degrades
92
+ gracefully if it is somehow absent.
93
+ - **Tier 3 — Dev loop** (requires the `pebble` CLI on `PATH`):
94
+ `pebble_build`, `pebble_install`, `emu_start`, `emu_stop`,
95
+ `emu_screenshot`, `emu_input`, `emu_logs`, `flow_validate`, and the crown
96
+ jewel `flow_run` — run a gallery-style flow spec and get every screenshot
97
+ back as MCP images. These tools register only when `pebble` is found.
98
+ - **Tier 4 — Authenticated** (requires `PEBBLE_API_TOKEN`; off by default):
99
+ reserved for token-gated, network-mutating operations (heart an app,
100
+ publish, timeline pins). Detected by `capabilities()`; opt-in only.
101
+
102
+ Read-only reference is also exposed as MCP resources: `pebble://colors`,
103
+ `pebble://fonts`, `pebble://platforms`, `pebble://wire-conventions`.
104
+
105
+ ### The `capabilities()` tier probe
106
+
107
+ Call the `capabilities()` tool first. It reports which tiers are live in the
108
+ running environment (`tier1_appstore`, `tier2_design`, `tier3_devloop`,
109
+ `tier4_auth`) so an agent can plan its work instead of calling a tool that
110
+ isn't wired up. Tier 1 is always true; the rest reflect Pillow, the `pebble`
111
+ CLI, and `PEBBLE_API_TOKEN` respectively.
112
+
113
+ ## Development
114
+
115
+ ```bash
116
+ uv sync # install deps (+ dev group: pytest, ruff)
117
+ uv run pytest # run the test suite
118
+ uv run ruff check # lint
119
+ ```
120
+
121
+ The `pebble_mcp/examples/flows/` directory holds sample flow specs (used by the
122
+ tests and as living documentation of the flow format, including the
123
+ `# SAFETY RULES (live-write hazards — DO NOT TRIGGER):` convention that flow
124
+ authors should follow for any app with server-mutating screens).
125
+
126
+ ## License
127
+
128
+ MIT — see [LICENSE](LICENSE). Copyright (c) 2026 Daniel Bonomo.
129
+
130
+ ---
131
+
132
+ *Not affiliated with, endorsed by, or sponsored by Core Devices or Rebble.
133
+ "Pebble" and related marks belong to their respective owners.*
@@ -0,0 +1,110 @@
1
+ # pebble-mcp
2
+
3
+ <p align="center">
4
+ <img src="docs/demo.gif" alt="pebble-mcp downloading a store app and screenshotting it in the emulator" width="200">
5
+ </p>
6
+
7
+ An [MCP](https://modelcontextprotocol.io) server for the Pebble smartwatch
8
+ ecosystem: appstore search, a 64-color design toolkit, and the local dev loop
9
+ (build → emulator → screenshot flows) exposed as typed tools usable from any
10
+ MCP host — Claude Code, Claude Desktop, claude.ai, or any other agent — not
11
+ just a shell. The demo above is a single `flow_run`: it installs a real
12
+ appstore app (Hubble) onto the emery emulator, drives the buttons, and captures
13
+ each screen — all through MCP.
14
+
15
+ ## Install
16
+
17
+ Once published to PyPI, no checkout is needed — `uvx` runs it on demand. Add
18
+ this to your MCP host's config (e.g. a Claude Code `.mcp.json`):
19
+
20
+ ```json
21
+ {
22
+ "mcpServers": {
23
+ "pebble-mcp": {
24
+ "command": "uvx",
25
+ "args": ["pebble-mcp"]
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ To run from a local checkout instead (development, or before the PyPI release):
32
+
33
+ ```json
34
+ {
35
+ "mcpServers": {
36
+ "pebble-mcp": {
37
+ "command": "uv",
38
+ "args": ["run", "--directory", "/absolute/path/to/pebble-mcp", "pebble-mcp"]
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ ### Quickstart (Claude Code)
45
+
46
+ ```bash
47
+ # from PyPI (once published)
48
+ claude mcp add pebble-mcp -- uvx pebble-mcp
49
+
50
+ # or from a local checkout
51
+ claude mcp add pebble-mcp -- uv run --directory /absolute/path/to/pebble-mcp pebble-mcp
52
+ ```
53
+
54
+ Then, in a session, ask the agent to call `capabilities()` to see which tiers
55
+ are live, or `store_search("weather")` to hit the appstore immediately.
56
+
57
+ ## Capability tiers
58
+
59
+ The server registers tools in four tiers and probes the environment at startup,
60
+ so an agent only ever sees the tools it can actually run.
61
+
62
+ - **Tier 1 — Appstore** (pure HTTPS, no auth, always on):
63
+ `store_search`, `store_app`, `store_collection`, `store_category`,
64
+ `store_developer`, `store_compare`, `store_download_pbw`.
65
+ - **Tier 2 — Design toolkit** (pure Python + Pillow):
66
+ `color_nearest`, `palette_swatch`, `image_quantize`, `image_prep`,
67
+ `font_plan`, `pdc_convert`. Pillow is a core dependency, so this tier is
68
+ normally always on; `capabilities()` still probes for it and degrades
69
+ gracefully if it is somehow absent.
70
+ - **Tier 3 — Dev loop** (requires the `pebble` CLI on `PATH`):
71
+ `pebble_build`, `pebble_install`, `emu_start`, `emu_stop`,
72
+ `emu_screenshot`, `emu_input`, `emu_logs`, `flow_validate`, and the crown
73
+ jewel `flow_run` — run a gallery-style flow spec and get every screenshot
74
+ back as MCP images. These tools register only when `pebble` is found.
75
+ - **Tier 4 — Authenticated** (requires `PEBBLE_API_TOKEN`; off by default):
76
+ reserved for token-gated, network-mutating operations (heart an app,
77
+ publish, timeline pins). Detected by `capabilities()`; opt-in only.
78
+
79
+ Read-only reference is also exposed as MCP resources: `pebble://colors`,
80
+ `pebble://fonts`, `pebble://platforms`, `pebble://wire-conventions`.
81
+
82
+ ### The `capabilities()` tier probe
83
+
84
+ Call the `capabilities()` tool first. It reports which tiers are live in the
85
+ running environment (`tier1_appstore`, `tier2_design`, `tier3_devloop`,
86
+ `tier4_auth`) so an agent can plan its work instead of calling a tool that
87
+ isn't wired up. Tier 1 is always true; the rest reflect Pillow, the `pebble`
88
+ CLI, and `PEBBLE_API_TOKEN` respectively.
89
+
90
+ ## Development
91
+
92
+ ```bash
93
+ uv sync # install deps (+ dev group: pytest, ruff)
94
+ uv run pytest # run the test suite
95
+ uv run ruff check # lint
96
+ ```
97
+
98
+ The `pebble_mcp/examples/flows/` directory holds sample flow specs (used by the
99
+ tests and as living documentation of the flow format, including the
100
+ `# SAFETY RULES (live-write hazards — DO NOT TRIGGER):` convention that flow
101
+ authors should follow for any app with server-mutating screens).
102
+
103
+ ## License
104
+
105
+ MIT — see [LICENSE](LICENSE). Copyright (c) 2026 Daniel Bonomo.
106
+
107
+ ---
108
+
109
+ *Not affiliated with, endorsed by, or sponsored by Core Devices or Rebble.
110
+ "Pebble" and related marks belong to their respective owners.*
@@ -0,0 +1,98 @@
1
+ # pebble-mcp — Tool Surface Contract
2
+
3
+ The usability contract for every MCP tool this server exposes. The requirements
4
+ doc says *what* ships; this says *how it must feel*. A tool that violates this
5
+ doc is not done, even if it works. Scope changes go through Fable + Dan.
6
+
7
+ Guiding test for every design decision: **an agent that has never seen Pebble
8
+ before reads only the tool list and schemas — can it do the task on the first
9
+ call, and when it can't, does the error tell it what to do next?**
10
+
11
+ ## 1. Small surface, strong verbs
12
+
13
+ - Target **≤ 15 tools total** across all tiers. Prefer one tool with a clear
14
+ argument over two near-duplicate tools. (Counter-example to avoid: separate
15
+ `store_faces` / `store_apps` — it's one `store_search` with a `type` arg.)
16
+ - Every tool name is `noun_verb` or `noun` scoped by prefix: `store_*`,
17
+ `image_*`, `font_*`, `emu_*`, `pebble_*` (build/install), `flow_*`.
18
+ - No tool exists just to expose a library function. If an agent wouldn't reach
19
+ for it while building or shipping a watchface, it doesn't get a tool.
20
+ Library ≠ surface.
21
+
22
+ ## 2. Descriptions and schemas are the product
23
+
24
+ - Tool description: first sentence = what it does for the caller. Then when to
25
+ use it, then sharp edges. Written for a model, tuned like docs.
26
+ - Every enum-ish argument IS an enum in the schema (platforms, buttons,
27
+ collection slugs, dither modes). Never "string, see docs".
28
+ - Defaults encode expertise: `hardware="emery"`, sane limits, safe modes. A
29
+ zero-argument or one-argument call should do the obviously-right thing.
30
+ (Lesson from GymTracker: value on first call, never "configure elsewhere
31
+ first".)
32
+
33
+ ## 3. Returns: compact, structured, honest
34
+
35
+ - **Context is the scarce resource.** List-shaped returns are trimmed
36
+ projections (id, title, author, hearts, one-line summary) — never the raw
37
+ App object. Full detail is a *by-id* lookup. Cap list returns (default
38
+ ~10, max ~50) and always include pagination state the agent can act on:
39
+ `{offset, returned, has_more}`.
40
+ - Screenshots and swatches return as **MCP image content blocks** (the agent
41
+ sees them) *plus* the saved file path (the human keeps them). Both, always.
42
+ - Numbers the store API lies about get fixed in the tool: hearts sorting is
43
+ client-side (`sort=hearts` is silently ignored upstream — measured, not
44
+ assumed). The tool's output order is the order it promises.
45
+ - Anything cached carries `fetched_at`. Cached list tools accept
46
+ `refresh: bool = false`. Stale-on-network-failure returns data + a
47
+ `stale: true` flag, not an exception.
48
+
49
+ ## 4. Errors an agent can act on
50
+
51
+ - Every failure path returns: what failed, why (upstream detail bounded to a
52
+ few hundred chars), and **the next move** ("run capabilities()", "the id
53
+ looks malformed — store ids are 24 hex chars", "emulator wedged; it was
54
+ kill+wiped, retry your call").
55
+ - Tier-gated tools fail fast with the gate named: calling `pebble_build`
56
+ without the CLI returns the install hint, not a traceback.
57
+ - No silent None. A lookup that finds nothing says so and echoes what it
58
+ looked for.
59
+
60
+ ## 5. Sharp edges stay inside the tool
61
+
62
+ - `pebble_install` bakes in kill+wipe. `flow_run` bakes in wedge recovery
63
+ (one restart, then a structured failure). Agents never need to know the
64
+ folklore; that's the whole point of the server.
65
+ - Emulator tools are serialized internally (one QEMU); a second concurrent
66
+ call queues or fails clearly — it never interleaves.
67
+ - Log/output capture is always bounded (bytes and seconds) with the truncation
68
+ stated in the return.
69
+
70
+ ## 6. Safety and tiers
71
+
72
+ - Tier 4 (authed) tools: mutating calls (`store_heart`, `pebble_publish`,
73
+ timeline pushes) require an explicit `confirm: true` argument and describe
74
+ exactly what will happen when called without it. Publish is a two-step
75
+ handshake, never one call.
76
+ - `capabilities()` stays cheap, evaluated per-call, and is the single source
77
+ of truth the other tools' gate errors point at.
78
+ - Nothing phones home; the only network calls are the ones the tool name
79
+ promises.
80
+
81
+ ## 7. Known upstream quirks (encode, don't rediscover)
82
+
83
+ | Quirk | Tool-level answer |
84
+ |---|---|
85
+ | `sort=hearts` ignored by collection API | client-side sort, documented in description |
86
+ | collection type is `apps`/`faces` (not `watchapps`) | enum hides the slug entirely |
87
+ | `Page.total` absent | expose `has_more` from returned-vs-limit instead |
88
+ | no text-search read endpoint mapped yet (A1 gap) | `store_search` is the #1 user verb — needs an endpoint or client-side scan; resolve before A2 ships |
89
+ | watchapps need wipe-before-install to foreground | baked into `pebble_install` |
90
+
91
+ ## 8. Definition of slick (acceptance)
92
+
93
+ A fresh agent session with only this server configured can, unprompted:
94
+ 1. name the top-5 most-loved emery faces (correct order, one call),
95
+ 2. build a broken project and report the *actionable* compiler error,
96
+ 3. run a 3-step flow and *show* the screenshots,
97
+ 4. explain why a Tier-4 tool call didn't run and what's needed to enable it —
98
+ each in ≤ 2 tool calls, no call returning > ~4 KB of text.