Skip to content

Commit 3d32e75

Browse files
authored
Add optional Google Stitch MCP as a comp source. (#16)
Remote https://stitch.googleapis.com/mcp via env STITCH_API_KEY. Not a core server. Not a UI implementer. Bank + Impeccable remain the product path.
1 parent d58f246 commit 3d32e75

18 files changed

Lines changed: 328 additions & 12 deletions

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@
44

55
None.
66

7+
## 1.8.2 — 2026-09-10
8+
9+
Patch release adding Google Stitch as an optional remote MCP comp source. Total skills remain **62** (46 model-invoked, 16 manual slash commands).
10+
11+
- Add optional Google Stitch remote MCP (`https://stitch.googleapis.com/mcp`) via `opencode-bf stitch enable`.
12+
- Support `{env:STITCH_API_KEY}` header interpolation or `--oauth` mode without headers.
13+
- Add `opencode-bf stitch disable` with surgical removal of only `mcp.stitch`.
14+
- Doctor: stitch is optional; absent != fail, schema violation = fail. Core servers remain 3 (`codebase-memory-mcp`, `context7`, `shadcn`).
15+
- Router: Stitch MCP is for screen/comp mock generation only, never an automatic production UI implementer. Product UI remains bank + Impeccable + Design V2 atoms + shadcn.
16+
- Impeccable: treat existing Stitch screens as approved comps; local atom shortlist remains mandatory.
17+
718
## 1.8.1 — 2026-09-10
819

920
Patch release for Design V2 atomic role locking and Impeccable atom handoff. Total skills remain **62** (46 model-invoked, 16 manual slash commands).

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.8.1
1+
1.8.2

docs/mcp.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@ Owned:
1313
Optional:
1414

1515
- `serena``opencode-bf serena enable` if the binary is on PATH
16+
- `stitch``opencode-bf stitch enable` (remote comp/mock source only; auth via `{env:STITCH_API_KEY}` or `--oauth`)
1617
- `exa` — foreign; never add/remove/overwrite
1718

1819
Merge is parse-aware. Comment-free JSON is rewritten with `json.dumps`. JSONC with comments is patched surgically (owned MCP keys only). If surgical merge cannot be verified, install fails closed instead of destroying comments.
1920

2021
Doctor reports `CONFIGURED` for owned MCP entries present in config. That is not a live connection. `opencode-bf doctor --deep` probes `opencode mcp list` per server/per line and **exits 1** unless every core server is `CONNECTED`. `DISCONNECTED`, `NOT_CHECKED`, `LISTED`, empty output, and command failure are not healthy. The substring `connected` inside `disconnected` is not treated as connected. ANSI codes are stripped before parse.
2122

2223
`opencode-bf serena enable` adds Serena only if absent. JSONC comments, provider keys, and foreign MCP are preserved via the same surgical merge as core MCP. Invalid config fails closed.
24+
25+
`opencode-bf stitch enable` configures Google Stitch as an optional remote comp/mock server (`https://stitch.googleapis.com/mcp`). It is not an owned core server and not a UI implementer. Keys are never written directly to config, only referenced via `{env:STITCH_API_KEY}` or omitted when using `--oauth`. `opencode-bf stitch disable` surgically removes only the stitch server key.

docs/routing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ MANUAL_NOT_INVOKED
2222

2323
Never list unused tools as used.
2424

25-
UI direction from the bank routes to `found-this-design` first, which stops before component implementation. Visual UI and UI atoms (buttons, inputs, cards, nav) route to `impeccable` after Design V2 shortlist; BANK_MISS ≠ generate (+ shadcn/Design V2 internal). Motion UI routes to `emil-design-eng`. Still/ads/non-UI surface route to `visual-studio`. Scroll-led stories route to `scroll-craft`, while continuous camera 3D fly-throughs route to `scroll-world`.
25+
UI direction from the bank routes to `found-this-design` first, which stops before component implementation. Visual UI and UI atoms (buttons, inputs, cards, nav) route to `impeccable` after Design V2 shortlist; BANK_MISS ≠ generate (+ shadcn/Design V2 internal). Stitch MCP is for screen/comp generation only, then found-this-design or impeccable with Design V2 atom shortlisting; never implement production UI from Stitch alone. Motion UI routes to `emil-design-eng`. Still/ads/non-UI surface route to `visual-studio`. Scroll-led stories route to `scroll-craft`, while continuous camera 3D fly-throughs route to `scroll-world`.
2626

2727
Browser verification follows four explicit doors: exploratory application UI routes to `playwright-qa`, persistent multi-account sessions route to `browser-act`, observed Chromium cause routes to `chrome-devtools-axi`, and button handler sequential undo / shared-store side effects route to `click-path-audit`.
2828

lib/cli.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
cmd_restore,
2626
cmd_restore_list,
2727
cmd_serena_enable,
28+
cmd_stitch_disable,
29+
cmd_stitch_enable,
2830
cmd_uninstall,
2931
)
3032
from lib.integrity import cmd_verify # noqa: E402
@@ -98,6 +100,10 @@ def build_parser() -> argparse.ArgumentParser:
98100
se = sub.add_parser("serena")
99101
se.add_argument("action", choices=["enable"])
100102

103+
st = sub.add_parser("stitch", help="optional Google Stitch remote MCP")
104+
st.add_argument("action", choices=["enable", "disable"])
105+
st.add_argument("--oauth", action="store_true", help="use OAuth/Bearer auth instead of STITCH_API_KEY header")
106+
101107
sd = sub.add_parser("smartdoc", help="document profiles, extract, status")
102108
add_smartdoc_cli(sd)
103109
sb = sub.add_parser("smartbook", help="reusable SmartBook lifecycle")
@@ -153,6 +159,10 @@ def main(argv: list[str] | None = None) -> int:
153159
return isolation_check(deep=args.deep)
154160
if cmd == "serena":
155161
return cmd_serena_enable()
162+
if cmd == "stitch":
163+
if args.action == "enable":
164+
return cmd_stitch_enable(oauth=args.oauth)
165+
return cmd_stitch_disable()
156166
if cmd == "smartdoc":
157167
return dispatch_smartdoc(args)
158168
if cmd == "smartbook":

lib/doctor.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,17 +118,32 @@ def mcp_status_map() -> dict[str, str]:
118118
try:
119119
data = jsonc.load_path(cfg)
120120
except (OSError, json.JSONDecodeError, ValueError):
121-
return {k: "FAIL" for k in ("codebase-memory-mcp", "context7", "shadcn", "serena", "exa")}
121+
return {k: "FAIL" for k in ("codebase-memory-mcp", "context7", "shadcn", "serena", "stitch", "exa")}
122122
mcp = data.get("mcp") or {}
123123
owned = {"codebase-memory-mcp", "context7", "shadcn"}
124-
for name in ("codebase-memory-mcp", "context7", "shadcn", "serena", "exa"):
124+
for name in ("codebase-memory-mcp", "context7", "shadcn", "serena", "stitch", "exa"):
125125
spec = mcp.get(name)
126126
if spec is None:
127-
out[name] = "OPTIONAL_ABSENT" if name in {"serena", "exa"} else "FAIL"
127+
out[name] = "OPTIONAL_ABSENT" if name in {"serena", "stitch", "exa"} else "FAIL"
128+
continue
129+
if not isinstance(spec, dict):
130+
out[name] = "FAIL"
128131
continue
129132
if spec.get("enabled") is False:
130133
out[name] = "DISABLED"
131134
continue
135+
if name == "stitch":
136+
typ = spec.get("type")
137+
url = spec.get("url")
138+
if typ != "remote" or url != "https://stitch.googleapis.com/mcp":
139+
out[name] = "FAIL"
140+
continue
141+
headers = spec.get("headers")
142+
if headers is not None and not isinstance(headers, dict):
143+
out[name] = "FAIL"
144+
continue
145+
out[name] = "CONFIGURED"
146+
continue
132147
if name not in owned:
133148
out[name] = "FOREIGN"
134149
continue

lib/install.py

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -872,7 +872,7 @@ def take(src: Path, dest: Path) -> None:
872872
"modelInvokedSkills": meta["model"],
873873
"manualSkills": meta["manual"],
874874
"ownedMcp": list(OWNED_MCP),
875-
"optionalMcp": ["serena", "exa"],
875+
"optionalMcp": ["serena", "stitch", "exa"],
876876
"designBank": {
877877
"root": bank_root,
878878
"source": bank_source,
@@ -1291,3 +1291,81 @@ def cmd_serena_enable() -> int:
12911291
path.write_text(jsonc.dumps(data), encoding="utf-8")
12921292
info(f"enabled serena MCP in {path}")
12931293
return 0
1294+
1295+
1296+
def cmd_stitch_enable(oauth: bool = False) -> int:
1297+
if not oauth and not os.environ.get("STITCH_API_KEY", "").strip():
1298+
die("STITCH_API_KEY environment variable is empty (set STITCH_API_KEY or use --oauth)")
1299+
spec: dict[str, object] = {
1300+
"type": "remote",
1301+
"url": "https://stitch.googleapis.com/mcp",
1302+
"enabled": True,
1303+
}
1304+
if not oauth:
1305+
spec["headers"] = {
1306+
"X-Goog-Api-Key": "{env:STITCH_API_KEY}",
1307+
}
1308+
path = target_config_path()
1309+
path.parent.mkdir(parents=True, exist_ok=True)
1310+
if not path.is_file():
1311+
path.write_text(
1312+
jsonc.dumps({"$schema": "https://opencode.ai/config.json", "mcp": {"stitch": spec}}),
1313+
encoding="utf-8",
1314+
)
1315+
info(f"enabled stitch MCP in {path}")
1316+
return 0
1317+
raw = path.read_text(encoding="utf-8")
1318+
try:
1319+
data = jsonc.loads(raw)
1320+
except Exception as exc:
1321+
die(f"OPENCODE_CONFIG_INVALID: {exc}")
1322+
if not isinstance(data, dict):
1323+
die("OPENCODE_CONFIG_INVALID: root is not an object")
1324+
mcp = data.get("mcp") or {}
1325+
if not isinstance(mcp, dict):
1326+
die("OPENCODE_CONFIG_INVALID mcp")
1327+
if jsonc.contains_comments(raw):
1328+
try:
1329+
merged = jsonc.upsert_mcp_servers(raw, {"stitch": spec})
1330+
jsonc.loads(merged)
1331+
path.write_text(merged if merged.endswith("\n") else merged + "\n", encoding="utf-8")
1332+
except Exception as exc:
1333+
die(f"OPENCODE_CONFIG_JSONC_SURGICAL_FAILED: {exc}")
1334+
else:
1335+
data.setdefault("mcp", {})["stitch"] = spec
1336+
path.write_text(jsonc.dumps(data), encoding="utf-8")
1337+
info(f"enabled stitch MCP in {path}")
1338+
return 0
1339+
1340+
1341+
def cmd_stitch_disable() -> int:
1342+
path = target_config_path()
1343+
if not path.is_file():
1344+
info("stitch MCP not present; nothing to disable")
1345+
return 0
1346+
raw = path.read_text(encoding="utf-8")
1347+
try:
1348+
data = jsonc.loads(raw)
1349+
except Exception as exc:
1350+
die(f"OPENCODE_CONFIG_INVALID: {exc}")
1351+
if not isinstance(data, dict):
1352+
die("OPENCODE_CONFIG_INVALID: root is not an object")
1353+
mcp = data.get("mcp") or {}
1354+
if not isinstance(mcp, dict):
1355+
die("OPENCODE_CONFIG_INVALID mcp")
1356+
if "stitch" not in mcp:
1357+
info("stitch MCP not present; nothing to disable")
1358+
return 0
1359+
if jsonc.contains_comments(raw):
1360+
try:
1361+
merged = jsonc.remove_mcp_servers(raw, ["stitch"])
1362+
jsonc.loads(merged)
1363+
path.write_text(merged if merged.endswith("\n") else merged + "\n", encoding="utf-8")
1364+
except Exception as exc:
1365+
die(f"OPENCODE_CONFIG_JSONC_SURGICAL_FAILED: {exc}")
1366+
else:
1367+
del data["mcp"]["stitch"]
1368+
path.write_text(jsonc.dumps(data), encoding="utf-8")
1369+
info(f"disabled stitch MCP in {path}")
1370+
return 0
1371+

rules/00-routing.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ Do not infer a model provider from a logical model name. Treat custom-gateway al
5959
- Matching or choosing a visual direction from the local design bank (Refero / Motionsites): `/found-this-design` first. Stop before component implementation. Then `/impeccable` after a pick. Bank root comes from `~/.config/opencode/bestfriend/config/design-bank.json` (optional override `OPENCODE_DESIGN_BANK`).
6060
- Visual UI once a world is chosen, the brief is already visual, or creating UI atoms (buttons, inputs, cards, nav): UI atoms → impeccable after Design V2 shortlist; BANK_MISS ≠ generate. Design V2 shortlist `kind=component` is an internal stage, never a separate specialist route. Do not run `/found-this-design` for atomic components.
6161
- Design Intelligence is an internal, lazy retrieval stage of Impeccable `new-work`, never a primary route or specialist. Design V2 is the same: an offline user bank, never a specialist.
62+
- Stitch MCP: screen/comp generation only; then found-this-design or impeccable + Design V2 atoms. Never implement production UI from Stitch alone. Treat existing Stitch screens as approved comps; local atom shortlist remains mandatory.
6263
- Installable UI components: MCP `shadcn` only. Do not add Magic UI, Kibo, 21st.dev, or community UI MCP servers.
6364
- Use the hub only when cwd has `components.json`. Never silent `shadcn init` on this adapter, a backend or Python tree, or a non-UI cwd.
6465
- Scroll-led storytelling (scroll is the timeline, scrollytelling, signature interaction): `/scroll-craft`. Ordinary scrollable UI stays `/impeccable`. `/scroll-craft` plus Continuous World: Scroll Craft writes the brief, then `/scroll-world`.
@@ -103,7 +104,7 @@ Do not infer a model provider from a logical model name. Treat custom-gateway al
103104
## Plugins and extra MCP
104105

105106
- No extra marketplace plugins. Foundation = skills + MCP + thin AGENTS.md + runtime helpers.
106-
- User MCP: `codebase-memory-mcp`, `context7`, and `shadcn` on; `serena` absent until a human enables it; `exa` foreign.
107+
- User MCP: `codebase-memory-mcp`, `context7`, and `shadcn` on; `serena` and `stitch` absent until a human enables them; `exa` foreign.
107108
- ECC / other harness overlays: `FOREIGN_ON_DEMAND`. Never add, remove, or merge foreign harness control planes or continuous-learning runtimes. Individual warehouse procedures ported in Wave 2 (agent-architecture-audit, cost-aware-llm-pipeline, eval-harness, prompt-optimizer, skill-stocktake) and Wave 3 (api-design, contract-first, automation-audit-ops, code-tour, click-path-audit) are first-party MIT skills. If external ECC is already present in user environment, do not merge and do not shadow.
108109
- FOREIGN vendor packs (e.g. `mongodb/agent-skills`, `supabase/agent-skills`, `vercel-labs/agent-skills`) stay off the overlay; user may `npx skills add mongodb/agent-skills|supabase/agent-skills` locally; never `frontend-design` for product UI.
109110
- Never auto-edit rules or skills from a learning log (no `/learn`, `/evolve`, or session-end skill writers).
@@ -115,6 +116,7 @@ Do not infer a model provider from a logical model name. Treat custom-gateway al
115116
- Do not load Wave 2 or Wave 3 warehouse skills unless the user explicitly names the job.
116117
- Do not use vendor `frontend-design` for product UI; UI direction stays `/found-this-design` and implementation stays `/impeccable`.
117118
- Do not print database connection strings, JWTs, or secret keys in Supabase, MongoDB, or Vercel ops.
119+
- Do not use Stitch as an automatic UI implementer or let it replace Design V2 atom shortlist.
118120
- Do not run `/found-this-design` for atomic components (button, input, card, nav); stay in `/impeccable`.
119121
- Do not auto-start `/grilling` for product interviews or planning; `/grill-with-docs` is the primary route.
120122
- Do not `@`-import the full routing or verification files into CLAUDE.md.

skills/impeccable/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Core principles:
2222
- If empty: report `BANK_MISS`; fall back to shadcn MCP only if cwd contains `components.json`. Never invent arbitrary hex, radius, or typography.
2323
- If hits exist: pick or take the top role-exact card, record `id`, `provider`, `role`, and `local_path` into `.impeccable/atoms.json` in user project cwd.
2424
- Implementation must mimic the structure and tokens of that atom + the visual world pack if pinned by `found-this-design`.
25+
- If the user provides an existing Stitch screen / mock, treat it as an approved comp (comp-first build path); do not generate code directly from Stitch without shortlisting local Design V2 atoms for components.
2526
- Forbidden: `image_gen`, vendor `frontend-design`, or ungrounded model taste.
2627
- World or page-level layout still routes to `found-this-design` first when no world is pinned.
2728

templates/AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ repo/file → Codebase Memory MCP first (skip if no project for cwd) → Serena
3333

3434
## Specialists (load one)
3535

36-
UI direction → skill `found-this-design` then `impeccable`. UI atoms (button, input, card, nav) after world/brief → impeccable after Design V2 shortlist; BANK_MISS ≠ generate (never `found-this-design` for buttons). Motion UI (easing, hover, seam) → `emil-design-eng`. Still/ads/non-UI surface → `visual-studio`. Scroll-led story → `scroll-craft`. Camera/3D world/diorama → `scroll-world`. Registry → shadcn MCP. Design Intelligence and Design V2 are internal to Impeccable `new-work`, never a route.
36+
UI direction → skill `found-this-design` then `impeccable`. UI atoms (button, input, card, nav) after world/brief → impeccable after Design V2 shortlist; BANK_MISS ≠ generate (never `found-this-design` for buttons). Motion UI (easing, hover, seam) → `emil-design-eng`. Still/ads/non-UI surface → `visual-studio`. Scroll-led story → `scroll-craft`. Camera/3D world/diorama → `scroll-world`. Registry → shadcn MCP. Design Intelligence and Design V2 are internal to Impeccable `new-work`, never a route. Stitch MCP = screen/comp generation only; then found-this-design or impeccable + Design V2 atoms. Never implement production UI from Stitch alone.
3737

3838
Browser QA → skill `playwright-qa`. Explicit/session BrowserAct → `browser-act`. Observed cause → `chrome-devtools-axi` after `opencode-chromium-cdp` (`127.0.0.1:9223`). Never Google Chrome. Project E2E suites (Playwright Test/Cypress) stay authoritative for regressions.
3939

0 commit comments

Comments
 (0)