@khanglvm/relay 0.10.2 → 0.11.0

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.
package/README.md CHANGED
@@ -38,6 +38,34 @@ Keep relay current with **`rly upgrade`** — it installs the latest CLI and
38
38
  refreshes the skill (via `npx skills`, falling back to the bundled copy) in one
39
39
  step, leaving any boards you have open untouched.
40
40
 
41
+ ## Inside the Claude & Codex apps (MCP App)
42
+
43
+ relay also runs as an **MCP App** ([SEP-1865](https://modelcontextprotocol.io/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp))
44
+ — so the same board renders **inline, right in the conversation**, on Claude
45
+ desktop **and mobile** and in Codex, no browser tab. `rly mcp` is a
46
+ zero-dependency stdio MCP server; register it once and the agent gets two tools,
47
+ `relay_ask` (collect decisions/feedback with real form controls) and
48
+ `relay_show` (present a plan, diagram, diff, table, or prototype):
49
+
50
+ ```sh
51
+ # Claude Code
52
+ claude mcp add relay -- rly mcp
53
+
54
+ # Claude Desktop / Codex — write the host config for you
55
+ rly mcp install --target claude # claude_desktop_config.json
56
+ rly mcp install --target codex # ~/.codex/config.toml
57
+
58
+ # or print the snippet for any MCP host (incl. the raw JSON/TOML)
59
+ rly mcp config
60
+ ```
61
+
62
+ When the agent calls a relay tool, the host renders relay's `ui://relay/board`
63
+ resource in a sandboxed iframe, hands it the board spec, and the user's answers
64
+ flow back to the agent over the bridge (`ui/update-model-context`) — markdown,
65
+ code, diffs, tables, charts, mermaid/graphviz diagrams, images, and forms, all
66
+ in-chat. The classic browser board (`rly ask` / `rly show`) is unchanged; pick
67
+ whichever surface fits.
68
+
41
69
  ## What it improves
42
70
 
43
71
  | Without relay | With relay |
@@ -61,6 +89,7 @@ Node ≥ 18; Chart.js / Mermaid / Graphviz are vendored and lazy-loaded offline.
61
89
  | `rly help` | every command at a glance |
62
90
  | `rly install --target <agent>` | write relay's rules into an agent's instruction file — `claude` `codex` `cursor` `copilot` `kiro` `windsurf` `cline` `gemini` `opencode` `droid` `agents`; `--all`, `--scope`, `--print`, `--list` |
63
91
  | `rly upgrade` | update the CLI **and** refresh the skill in one step (safe around open boards; `--dry-run`, `--cli-only`, `--skill-only`) |
92
+ | `rly mcp` | run relay as an MCP App server so boards render **inline** in the chat — **stdio** for local desktop hosts (Claude Desktop, Codex), or `rly mcp --http` (Streamable HTTP) for web/mobile/remote; `rly mcp config` / `rly mcp install --target claude\|codex` to register it |
64
93
  | `rly agent` | the full agent guide — spec format, all block types, annotations, patterns ([docs/AGENT.md](docs/AGENT.md)) |
65
94
  | `rly schema` | board spec JSON Schema |
66
95
  | [skills/relay/SKILL.md](skills/relay/SKILL.md) | the bundled skill |
@@ -73,6 +102,43 @@ npm test # zero-dep smoke tests (spawns real servers, fake-submits)
73
102
 
74
103
  ## Changelog
75
104
 
105
+ ### 0.11.0 — render inline inside the Claude & Codex apps (MCP App)
106
+ - **`rly mcp` — relay as an MCP App** ([SEP-1865](https://modelcontextprotocol.io/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp),
107
+ extension `io.modelcontextprotocol/ui`). A zero-dependency stdio MCP server
108
+ that declares a `ui://relay/board` resource (`text/html;profile=mcp-app`) and
109
+ two tools, **`relay_ask`** and **`relay_show`**, linked to it via
110
+ `_meta.ui.resourceUri` (plus `openai/outputTemplate` for ChatGPT/Codex). The
111
+ host renders the board **inline in the conversation** — Claude desktop **and
112
+ mobile**, Codex — instead of opening a browser tab.
113
+ - **Same board, postMessage transport.** The inline board reuses relay's block
114
+ renderer (markdown, code, diff, table, chart, mermaid, graphviz, image, html)
115
+ over the MCP Apps JSON-RPC bridge: the spec arrives as the tool result, the
116
+ user's answers go back via `ui/update-model-context`, the iframe auto-sizes
117
+ via `ui/notifications/size-changed`, and vendored Chart.js / Mermaid / Viz.js
118
+ load on demand through the host's `resources/read` (no `/vendor` route, no
119
+ server in the sandbox).
120
+ - **One-command setup** — `rly mcp install --target claude|codex` writes the
121
+ host config; `rly mcp config` prints the snippet for any MCP host. The classic
122
+ browser board is untouched.
123
+ - **Streamable HTTP transport for web/mobile/remote** — `rly mcp --http
124
+ [--port N --host H --token SECRET --allow-origin ORIGIN]` serves the same tools
125
+ over MCP's Streamable HTTP transport (single `/mcp` endpoint, JSON responses,
126
+ CORS, Origin validation, bearer auth, `Mcp-Session-Id`). Expose it via a tunnel
127
+ or small host and add it as a custom connector so a phone/web host can render
128
+ boards too — stdio stays the zero-setup path for local desktop.
129
+ - **Native look** — the inline board **color-blends** onto the host's SEP-1865
130
+ style variables (surfaces, text, borders, primary button, fonts), pins
131
+ `color-scheme` so `light-dark()` tokens resolve, and uses the host's **own
132
+ full-screen** control (centering content to a readable column in fullscreen).
133
+ After submit it collapses to a one-line confirmation so the iframe shrinks.
134
+ - **Progressive rendering** — when the host streams the tool call
135
+ (`ui/notifications/tool-input-partial`), the board renders valid blocks as they
136
+ arrive (a "Composing…" preview) instead of waiting for the whole spec.
137
+ - **`palette` block** — color palettes as swatch cards (hover reveals hex, click
138
+ copies); mark one `featured` for a spotlight. **`color` question type** — native
139
+ picker + hex field + optional `presets`, returns a hex string. Both work on the
140
+ browser board and inline.
141
+
76
142
  ### 0.10.0 — open files, richer code, diffs & video
77
143
  - **Clickable local file-links.** Write a path in any markdown (`~/clip.mp4`,
78
144
  `./src/app.ts`, `/abs/report.pdf`, a `file://` URL, a backtick-wrapped path,
package/docs/AGENT.md CHANGED
@@ -5,6 +5,14 @@ rich content blocks (markdown, charts, diagrams, tables, code, custom HTML),
5
5
  then **wait for them to click Submit** and read the answers as JSON from stdout.
6
6
  No "type 'done' in the terminal", no hand-rolled HTML+server.
7
7
 
8
+ **Use relay even in plan mode** — for any clarifying or decision question (Claude
9
+ Code / Codex), route it through a relay board, **not** the native
10
+ `AskUserQuestion` / ask-user tool: that tool is **not** a substitute, even though
11
+ plan-mode guidance suggests it. Do NOT ask in plain terminal text and do NOT
12
+ reach for the native ask tool for anything beyond one trivial yes/no — open a
13
+ board. Reserve the harness's `ExitPlanMode` strictly for the final plan-approval
14
+ gate (it's a mode transition, not a question).
15
+
8
16
  **Tell the user** at the start of your intro text that they can hover chart
9
17
  points, diagram nodes, table cells, and any element of a custom-HTML block to
10
18
  leave comments, and select text in markdown blocks to annotate — their comments
@@ -54,6 +62,49 @@ Answers **autosave in real time** as the user fills the board — a page reload
54
62
  restores them, and a draft survives timeouts/cancellation (included in those
55
63
  results), so partial input is never lost.
56
64
 
65
+ ## Inline mode — relay as an MCP App (Claude & Codex apps)
66
+
67
+ Everything above is the **CLI** surface (you run `rly` in a terminal and read
68
+ JSON from stdout). relay is **also** an MCP App (SEP-1865): when a host registers
69
+ `rly mcp` (see `rly mcp config` / `rly mcp install --target claude|codex`), you
70
+ get two tools that render the board **inline in the conversation** instead of a
71
+ browser tab. `rly mcp` speaks the MCP **stdio** transport, so it pairs with a
72
+ **local desktop** host — Claude Desktop and Codex today.
73
+
74
+ - **`relay_ask`** — collect decisions/feedback with real form controls.
75
+ - **`relay_show`** — present a plan, diagram, diff, table, or prototype.
76
+
77
+ **Web / mobile / remote hosts** can't reach a stdio subprocess, so for those run
78
+ the **Streamable HTTP** transport instead: `rly mcp --http [--port N]` serves the
79
+ same tools at `http://127.0.0.1:<port>/mcp`. Expose that URL (e.g. a
80
+ cloudflared/ngrok tunnel, or a small always-on host) and register it as a custom
81
+ connector; add `--token <secret>` + `--allow-origin <host>` when it's public.
82
+ `rly mcp config` prints the recipe. Note: an HTTP server only sees local files if
83
+ it runs on the machine those files live on (tunnel from your dev box to keep
84
+ local `codeFile`/image blocks working).
85
+
86
+ Progressive rendering: if the host streams the tool call as you write it, the
87
+ board renders valid blocks/questions incrementally (a "Composing…" preview) and
88
+ finalizes when your call completes — so the user sees it build, not a blank wait.
89
+
90
+ Both take **the exact same board spec** documented below (the tool `inputSchema`
91
+ *is* this spec). Call the tool with your spec; the host shows the board, the user
92
+ fills it in, and their answers come back to you (answers, per-question notes,
93
+ comment) — read them just as you would the CLI's result JSON. There is **no
94
+ `--detach`/`rly wait` dance, no stdout parsing, and no timeout** in this mode; the
95
+ board stays live until the user submits and the host delivers the result.
96
+
97
+ **Near-full parity with the browser board.** Local `codeFile` / `htmlFile` /
98
+ `diffFile` and local **image** files work inline too — the server inlines them
99
+ (images as data URIs) while normalizing your spec, so the sandboxed board needs
100
+ no file access. The board also adopts the host's **theme, fonts and colors** and
101
+ offers a **full-screen** toggle. The only inline-mode gaps vs. the browser board:
102
+ **local video files** (use a YouTube/Vimeo/`https` URL instead — those play) and
103
+ **element-level annotations** (comment-on-any-element) — per-question notes and
104
+ the overall comment still carry feedback back to you. Everything else —
105
+ questions plus markdown/code/diff/table/chart/mermaid/graphviz/plantuml/image/html
106
+ — renders identically.
107
+
57
108
  ## Creating boards
58
109
 
59
110
  From a JSON spec file (`--file spec.json`), stdin (`--file -`), or quick
@@ -123,9 +174,11 @@ rly show --html-file prototype.html --title "Dashboard concept" --height 600
123
174
  | `text` | `"string"` |
124
175
  | `textarea` | `"string"` |
125
176
  | `scale` | number (`min`…`max`, default 1–5) |
177
+ | `color` | hex string (e.g. `"#c2674b"`) — native picker + hex field; optional `presets:["#…"]` swatches |
126
178
 
127
179
  Aliases accepted: radio/choice/select→single, checkbox→multi,
128
- boolean/bool/yn→yesno, input→text, longtext→textarea, rating/likert→scale.
180
+ boolean/bool/yn→yesno, input→text, longtext→textarea, rating/likert→scale,
181
+ colour/swatch→color.
129
182
 
130
183
  ### Result JSON (stdout)
131
184
 
@@ -305,6 +358,17 @@ Rules of thumb:
305
358
  // for a huge / high-resolution image pass an http(s) URL (streamed, no size cap).
306
359
  { "type": "image", "src": "screenshots/variant-a.png", "alt": "Variant A", "height": 220 }
307
360
  { "type": "image", "src": "https://example.com/mock.png" }
361
+
362
+ // palette — color palettes as swatch cards (hover reveals hex, click copies).
363
+ // Mark one {"featured": true} to render it larger as a spotlight; the rest tile
364
+ // into a responsive grid. Pairs with a "color" question to let the user pick.
365
+ { "type": "palette", "title": "Trending palettes", "palettes": [
366
+ { "name": "Mocha Mousse", "sub": "Pantone 2025 · warm", "tag": "Pantone", "tagTone": "warm",
367
+ "featured": true, "colors": ["#C4956A","#A67B52","#8B6240","#D4AB89","#EDD9C4"] },
368
+ { "name": "Digital Lavender", "mood": "soft tech", "tag": "Cool", "tagTone": "cool",
369
+ "colors": ["#C9BAF5","#A08EE8","#7B66CC","#5849A8"] } ] }
370
+ { "type": "palette", "name": "Brand", "colors": ["#c2674b", "#1c1b19", "#fcfbf9"] } // single-palette shorthand
371
+ // tagTone (optional pill color): warm | cool | neutral | nature | bold | digital
308
372
  ```
309
373
 
310
374
  ### Local file links — clickable, open in the default app
@@ -331,6 +395,7 @@ real path over telling the user to paste it into a terminal.
331
395
  | `diff` | proposed code changes / before-after — a unified diff rendered as a colored git-style comparison (no git needed) |
332
396
  | `video` | demos, screen recordings, walkthroughs — YouTube/Vimeo embeds, a media URL, or a local video file (streamed) |
333
397
  | `image` | screenshots, mockup exports, photos — local files embed and work offline |
398
+ | `palette` | color palettes / themes — swatch cards with hover-hex + click-to-copy; pair with a `color` question to let the user pick |
334
399
  | `html` | anything else — pixel-perfect mockups, custom widgets, embeds |
335
400
 
336
401
  ### Height rules
package/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "@khanglvm/relay",
3
- "version": "0.10.2",
4
- "description": "Browser-based question boards with rich blocks (markdown, charts, mermaid, tables, code, diffs, video, sandboxed HTML), clickable local file-links, and element-level annotations for AI coding agents (Claude Code, Codex, …): ask users structured questions, present interactive visuals, collect inline comments, wait for submit, read answers as JSON.",
3
+ "version": "0.11.0",
4
+ "description": "Question boards with rich blocks (markdown, charts, mermaid, tables, code, diffs, video, sandboxed HTML), clickable local file-links, and element-level annotations for AI coding agents (Claude Code, Codex, …): ask users structured questions, present interactive visuals, collect inline comments, read answers as JSON in a local browser board OR rendered INLINE inside the Claude & Codex apps as an MCP App (SEP-1865).",
5
5
  "keywords": [
6
6
  "ai-agents",
7
7
  "claude-code",
8
8
  "codex",
9
+ "mcp",
10
+ "mcp-app",
11
+ "mcp-ui",
12
+ "model-context-protocol",
9
13
  "cli",
10
14
  "interactive-questions",
11
15
  "human-in-the-loop",
@@ -13,6 +13,13 @@ time; the tab auto-closes after submit.
13
13
  If `rly` is not installed: `npm i -g @khanglvm/relay` or invoke via
14
14
  `npx -y @khanglvm/relay <command …>`.
15
15
 
16
+ **Inside the Claude or Codex app?** relay is also an MCP App: if the
17
+ `relay_ask` / `relay_show` tools are available, call them with the same board
18
+ spec (below) and the board renders **inline in the chat** — no terminal, no
19
+ browser tab; the user's answers come straight back to you. Set it up once with
20
+ `rly mcp install --target claude|codex` (or `rly mcp config`). Everything below
21
+ describes the spec both surfaces share.
22
+
16
23
  **Full reference: run `rly agent` (complete guide) and `rly schema` (spec JSON
17
24
  Schema).** The essentials are below.
18
25
 
@@ -31,8 +38,15 @@ Schema).** The essentials are below.
31
38
  | "Show me the diff" / git diff / code changes / before-after | **rly** (`diff` block — run `git diff`, render it; never dump it in the terminal) |
32
39
  | A demo, screen recording or walkthrough | **rly** (`video` block) |
33
40
  | Point the user at a file to open (log, capture, report) | **rly** (a clickable local file-link in markdown) |
41
+ | Plan-mode clarifying question (Claude Code / Codex) | **rly** (not AskUserQuestion / the native ask tool) |
34
42
  | Something you can decide yourself from context | neither — just decide |
35
43
 
44
+ This holds **in plan mode** too: route every clarifying or decision question
45
+ through relay — the native `AskUserQuestion` / ask-user tool is **not** a
46
+ substitute, even though plan-mode guidance suggests it. Reserve the harness's
47
+ `ExitPlanMode` strictly for the final plan-approval gate (it's a mode
48
+ transition, not a question).
49
+
36
50
  Once the user has answered one board in a session, prefer boards for later
37
51
  question rounds too — they've shown they engage with them. Batch related
38
52
  questions into ONE board rather than opening several in a row.
package/src/cli.js CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  HOME,
19
19
  } from './store.js';
20
20
  import { runBoard } from './server.js';
21
+ import { runMcp, runMcpHttp, mcpConfig } from './mcp.js';
21
22
  import { openUrl } from './open.js';
22
23
 
23
24
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -31,6 +32,7 @@ const VALUED_FLAGS = new Set([
31
32
  'file', 'html', 'html-file', 'title', 'intro', 'timeout', 'port',
32
33
  'submit-label', 'height', 'limit', 'target', 'id', 'replies',
33
34
  'on-result', 'notify-cmd', 'idle-grace', 'scope',
35
+ 'host', 'token', 'allow-origin',
34
36
  ]);
35
37
 
36
38
  function camel(key) {
@@ -799,8 +801,12 @@ function stampSkillVersion() {
799
801
  const SKILL_RULES = `## relay — ask & show in the browser, not the terminal
800
802
 
801
803
  - Need the user's decisions, feedback, requirements, or approval (anything
802
- beyond one trivial yes/no)? Do NOT ask in plain terminal text. Build a board
803
- spec and run: \`rly ask --file spec.json --detach\`, then \`rly wait <boardId>\`.
804
+ beyond one trivial yes/no)? Do NOT ask in plain terminal text **and do NOT use
805
+ the native \`AskUserQuestion\` / ask-user tool** for anything beyond one
806
+ trivial yes/no, open a relay board. Build a board spec and run:
807
+ \`rly ask --file spec.json --detach\`, then \`rly wait <boardId>\`. **This holds
808
+ in plan mode too** — route clarifying/decision questions through relay there as
809
+ well; reserve \`ExitPlanMode\` for the final plan-approval gate only.
804
810
  - Presenting a plan, structure, architecture, data, or a prototype? Show a
805
811
  relay board with mermaid/graphviz/chart/table/code/diff/video/image/html
806
812
  blocks — never ASCII diagrams or walls of prose.
@@ -1177,6 +1183,88 @@ async function cmdUpgrade(args) {
1177
1183
  return 0;
1178
1184
  }
1179
1185
 
1186
+ // `rly mcp` — run relay as an MCP App server, or print/write the host config.
1187
+ // rly mcp serve over stdio (what a local desktop host launches)
1188
+ // rly mcp --http [--port N] serve over Streamable HTTP (web/mobile/remote hosts)
1189
+ // rly mcp config [--print] show setup for Claude / Codex / generic hosts
1190
+ // rly mcp install --target claude|codex write it into that host's config
1191
+ async function cmdMcp(rest) {
1192
+ const sub = rest[0];
1193
+ if (sub === 'config' || sub === 'setup' || sub === 'install' || sub === 'add') {
1194
+ return cmdMcpConfig(parseArgs(rest.slice(1)), sub);
1195
+ }
1196
+ const args = parseArgs(rest);
1197
+ if (args.http) {
1198
+ return runMcpHttp({
1199
+ port: args.port ? Number(args.port) : (process.env.RLY_MCP_PORT ? Number(process.env.RLY_MCP_PORT) : undefined),
1200
+ host: typeof args.host === 'string' ? args.host : '127.0.0.1',
1201
+ token: typeof args.token === 'string' ? args.token : (process.env.RLY_MCP_TOKEN || ''),
1202
+ allowOrigin: typeof args.allowOrigin === 'string' ? args.allowOrigin : '',
1203
+ });
1204
+ }
1205
+ // Default: be the stdio server. main() never resolves this, so the CLI's
1206
+ // post-run exit timer never arms — the server lives until stdin closes.
1207
+ return runMcp();
1208
+ }
1209
+
1210
+ // Resolve Claude Desktop's config path for this platform.
1211
+ function claudeDesktopConfigPath() {
1212
+ const home = os.homedir();
1213
+ if (process.platform === 'darwin') return path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
1214
+ if (process.platform === 'win32') return path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json');
1215
+ return path.join(process.env.XDG_CONFIG_HOME || path.join(home, '.config'), 'Claude', 'claude_desktop_config.json');
1216
+ }
1217
+
1218
+ function cmdMcpConfig(args, sub) {
1219
+ const cfg = mcpConfig({ command: 'rly' });
1220
+ const target = String(args.target || '').toLowerCase();
1221
+ const wantInstall = (sub === 'install' || sub === 'add') && args.print !== true;
1222
+
1223
+ // --print, or `config`/`setup` with no writable target: show every option.
1224
+ if (!wantInstall || !target) {
1225
+ const claudePath = claudeDesktopConfigPath();
1226
+ const codexPath = path.join(os.homedir(), '.codex', 'config.toml');
1227
+ printJson({
1228
+ note: 'Register relay as an MCP App server, then call relay_ask / relay_show from inside the host.',
1229
+ claudeCode: 'claude mcp add relay -- rly mcp',
1230
+ claudeDesktop: { file: claudePath, add: cfg.json },
1231
+ codex: { file: codexPath, add: cfg.toml },
1232
+ generic: { add: cfg.json },
1233
+ webMobile: cfg.http,
1234
+ install: 'rly mcp install --target claude | rly mcp install --target codex',
1235
+ });
1236
+ return 0;
1237
+ }
1238
+
1239
+ if (target === 'claude' || target === 'claude-desktop') {
1240
+ const file = claudeDesktopConfigPath();
1241
+ let existing = {};
1242
+ try { existing = JSON.parse(fs.readFileSync(file, 'utf8')); } catch { existing = {}; }
1243
+ if (existing === null || typeof existing !== 'object' || Array.isArray(existing)) existing = {};
1244
+ existing.mcpServers = existing.mcpServers && typeof existing.mcpServers === 'object' ? existing.mcpServers : {};
1245
+ existing.mcpServers.relay = { command: cfg.command, args: cfg.args };
1246
+ fs.mkdirSync(path.dirname(file), { recursive: true });
1247
+ fs.writeFileSync(file, JSON.stringify(existing, null, 2) + '\n');
1248
+ printJson({ installed: 'claude-desktop', file, server: 'relay', note: 'restart Claude Desktop to load it' });
1249
+ return 0;
1250
+ }
1251
+ if (target === 'codex') {
1252
+ const file = path.join(os.homedir(), '.codex', 'config.toml');
1253
+ let body = '';
1254
+ try { body = fs.readFileSync(file, 'utf8'); } catch { body = ''; }
1255
+ if (/\[mcp_servers\.relay\]/.test(body)) {
1256
+ printJson({ installed: 'codex', file, server: 'relay', note: 'already present — left as-is' });
1257
+ return 0;
1258
+ }
1259
+ fs.mkdirSync(path.dirname(file), { recursive: true });
1260
+ const sep = body && !body.endsWith('\n') ? '\n\n' : body ? '\n' : '';
1261
+ fs.writeFileSync(file, body + sep + cfg.toml);
1262
+ printJson({ installed: 'codex', file, server: 'relay', note: 'restart Codex to load it' });
1263
+ return 0;
1264
+ }
1265
+ throw new CliError(`unknown --target "${target}". Use claude or codex (or \`rly mcp config\` to print all).`, 4);
1266
+ }
1267
+
1180
1268
  async function cmdServeInternal(args) {
1181
1269
  const id = args.id;
1182
1270
  if (!id) throw new CliError('__serve: missing --id');
@@ -1232,6 +1320,11 @@ USAGE
1232
1320
  --scope global|project · --print (copy/paste) · --all · --list (no flags)
1233
1321
  rly upgrade install the latest CLI globally + refresh the skill in one step
1234
1322
  --stop/--force handle running boards · --dry-run · --cli-only/--skill-only
1323
+ rly mcp run relay as an MCP App server (stdio) — boards render INLINE in
1324
+ a local desktop host (Claude Desktop, Codex) instead of a browser tab
1325
+ rly mcp --http [--port N] serve over Streamable HTTP for web/mobile/remote hosts
1326
+ [--host H --token SECRET --allow-origin ORIGIN]
1327
+ rly mcp config | install --target print host setup, or write it (claude | codex)
1235
1328
 
1236
1329
  COMMON FLAGS
1237
1330
  --title <s> --intro <s> --html-file <f> --height <px> --submit-label <s>
@@ -1306,6 +1399,8 @@ export async function main(argv) {
1306
1399
  case 'schema':
1307
1400
  console.log(JSON.stringify(SPEC_SCHEMA, null, 2));
1308
1401
  return 0;
1402
+ case 'mcp':
1403
+ return await cmdMcp(rest);
1309
1404
  case '__serve':
1310
1405
  return await cmdServeInternal(parseArgs(rest));
1311
1406
  default: