Context
Orbixio produces video content, and the script stage is the bottleneck: it is the part that has to be right, and the part that is slowest to produce. It is also the part that language models are genuinely good at — provided the output is structured, reviewable, and never automatically acted on.
The interesting design question was not "can a model write a script". It was: if the model is going to be the drafting tool, what is the right shape for the software around it?
The problem
The default answer is to build a web app: a dashboard, a text editor, a generate button, an approval workflow, user management. That is several weeks of interface for a workflow whose entire content is ask for a script, read it, approve or reject.
Meanwhile the person doing the work is already sitting in Claude Desktop. Building a second, worse chat interface next to the good one is work that makes the product harder to use.
The real problem was the guarantee. "We always review before publishing" is a promise, and promises decay under deadline. It needed to be structurally impossible for unreviewed content to move downstream.
What I built
Six MCP tools over stdio, and no UI
list_channels get_channel generate_script review_script approve_script reject_script
The MCP server is the entire product surface. You point Claude Desktop at it and say "list my
channels, then generate a script about writing sales emails for the first channel" — Claude calls
list_channels, then generate_script, and returns a structured draft.
No dashboard was built, because none was needed.
Structured output, validated by zod
Scripts are not free text. They are validated structures: hooks, five tool entries each carrying an honest caveat, calls to action, and YouTube metadata. Validation happens at the boundary — if the model returns something that does not fit the schema, it fails there rather than three steps later in something that consumes it.
The "honest caveat" field is a product decision expressed in the data model: every tool a script recommends must carry its limitation. Making it a required field means the caveat cannot be quietly dropped when it is inconvenient.
The approval gate lives in the protocol
A script exists in a state, and the transition out of draft happens only through
approve_script or reject_script. There is no code path from generation to
downstream work that skips it. The human-in-the-loop requirement is not a documented process — it is
the shape of the API.
Multi-tenant from the first migration
Every row is scoped to a userId, even though v0 runs single-user via an environment
variable. Retrofitting tenancy onto a schema that never had it is a migration nobody enjoys; adding
a column at the start costs nothing.
A monorepo shaped like the boundaries
packages/ db/ Prisma schema, client, seed core/ Pipeline logic — LLM provider, script generator, schemas mcp-server/ MCP server wrapping core over stdio apps/ web/ Stubbed Next.js app (not implemented)
Core knows nothing about MCP. The MCP server is a thin transport wrapper around it. That separation is what lets a second consumer — a web app, a CLI, a scheduled job — attach later without touching the pipeline. It is the same principle behind the shared Duffel tool layer in Tourista.
Decisions I would defend
No microservices, no workflow engine — just functions
The README says this out loud because it is a decision, not an omission. The workload is a handful of operations against one database with one external API call. A workflow engine here would add operational surface, a second failure domain and a deployment story, in exchange for orchestration that a function composition already provides.
Shipping v0 with the web app stubbed
The Next.js app exists as an empty package, and I would rather say that plainly than pad the repository. The MCP server delivers the entire workflow today. Building the web UI first would have been building the part nobody needed in order to look more like a product.
Making the approval gate a protocol constraint
This is the decision I would most want to be asked about. Enforcing review in the UI means anyone with database access can bypass it. Enforcing it in the tool surface means the only way to move a script forward is to call the tool that records who approved it. In a domain where the AI writes and a human is accountable, that distinction is the whole design.
Ten minutes from clone to working
The setup path is documented as a numbered sequence including the exact Claude Desktop config JSON for macOS and Windows, and a first command to try. A reviewer who cannot get an MCP server running in ten minutes concludes the project does not work — which is nearly always a documentation failure rather than a code one.
The result
- Six MCP tools over stdio, working in Claude Desktop or any MCP client.
- Structured, zod-validated script output rather than free text.
- An approval gate that cannot be routed around, because there is no path that skips it.
- Unit tests and an end-to-end smoke test, with lint and build across all packages.
- A tenancy-ready schema from the first migration.
What I would do next
Implement the web app as a second consumer of core — proving the boundary holds — and
add real multi-user auth in place of the environment-variable user. Then the downstream half: taking
an approved script into production rather than stopping at approval.
My role and stack
Role: Solo engineer — architecture, schema, pipeline, MCP server, tests, documentation. Built under Orbixio Ltd, where I am Founder.
Stack: TypeScript, Prisma, PostgreSQL, @modelcontextprotocol/sdk,
@anthropic-ai/sdk, zod, pino, vitest, Biome, pnpm workspaces.