Skip to main content
Glama
yoruuuchan

yoru-studio-mcp

by yoruuuchan

Yoru Studio

A self-hosted execution studio for a single creator.

Read this in Simplified Chinese.

Yoru Studio is one person's workspace for turning ideas into finished creative work: catch a spark in the inbox, pull it into a mother project, break it into sub-projects (video / photo essay / long-form article / asset delivery), plan storyboards, run field shoots with an offline-safe queue, log what actually happened, then close the loop with a retrospective. Deliverables live under platform versions so the same cut can ship as Douyin / Bilibili / a Xiaohongshu image set without duplicating the project itself.

It is written for one person — the creator — running it on their own box. There is no multi-tenant story, no team seats, no SaaS backend. When you install it, the whole system lives on one machine you control.

What is here

Reading the source is the authoritative answer to "what does it actually do?" — the summary below is a map, not the territory.

  • Inbox → Mother project → Sub-project → Platform version: the full spine of a creative workflow, with a fast-track that lets a good idea skip straight into an active sub-project when you know where it belongs.

  • Storyboards with a list view and a board view, drag-to-reorder, per-shot reference images, XLSX export, and a printable version for on-set use.

  • Schedule and calendar: precise times, all-day dates, and fuzzy windows ("this week", "the weekend") coexist in one calendar; overdue is computed, not remembered, so nothing quietly rots.

  • Reminders and in-site notifications, deduplicated at the database layer so a restart never fires the same alert twice.

  • Execution records for shoots / re-shoots / screen recordings / writing sessions — attach the record to whichever project layer actually matches what you did.

  • Retrospectives, light or full; every field is optional. The structure is there to remind you, not to demand.

  • Attachments in four shapes: uploaded images (with thumbnails), path pointers (e.g. NAS/2026/Aug shoots/), external links, and text snippets. Soft-deleted files sit in a recycle bin for 30 days.

  • Field mode: mobile-first page for on-set use. If the network drops, edits queue in IndexedDB and sync when connectivity returns.

  • Full export: JSON + uploads bundle for takeout, from either the CLI or the settings page.

  • Backups: online SQLite backup on a schedule, and optional restic scripts for encrypted off-site copies with a real restore-drill runner.

  • MCP channel for AI agents (Claude, ChatGPT, Codex, …) — see the section below.

Related MCP server: mcp-devtools

Design stance

  • Single user, single account. The data model carries a workspace column so a future multi-user version does not have to rebuild the schema, but everything in the shipped code assumes exactly one user.

  • No external services required. SQLite on disk, files on disk. No Redis, no message queue, no third-party auth. You can run it on a $5/month VPS.

  • Small footprint. Target is app 512 MiB / scheduler 256 MiB / (optional) reverse-proxy sidecar 128 MiB. A 2 GiB VM is enough.

  • The server does not build the frontend. The Vite bundle is built locally (or in CI) and shipped as pre-built files. Deployment machines never need Node.

  • Content over ceremony. The retro form has no required fields — the schema exists to remind you what to think about, not to gatekeep saving.

Tech stack

  • Backend: Python 3.12, FastAPI, SQLite (with uv for dependency management).

  • Frontend: React 19 + TypeScript, built with Vite.

  • Deployment: Docker Compose (single-host). Reverse-proxying and TLS are your call — Cloudflare Tunnel, Caddy, Nginx, Tailscale Funnel, or just SSH tunnels for local-only use all work.

  • Testing: pytest for the backend, vitest for the frontend.

MCP channel: connect AI agents

Yoru Studio exposes a Model Context Protocol (MCP) server so agents that speak MCP — Claude Desktop, ChatGPT desktop, Codex CLI, Claude Code, and others — can read from and (append to) your studio without you copy-pasting.

Eight tools total, all scoped to append-only writes with idempotency:

Read (5):

  • list_projects — mother-project list with counts.

  • get_project — one mother's full detail, including its sub-projects.

  • get_sub_project — one sub-project, with storyboard / execution records / retrospective folded in.

  • get_schedule — upcoming (14 or 30 days), all overdue, near-term fuzzy events.

  • get_inbox — pending or discarded inbox items.

Write (3, all append-only, all idempotent):

  • capture_inspiration — drop a spark into the inbox.

  • append_storyboard_shots — atomically add N shots to a video sub-project's storyboard.

  • append_execution_record — log a shoot / write session / test.

Every write tool takes an idempotency_key. Retry with the same key returns the first result; a different payload with the same key is a hard conflict. Nothing an agent does can silently overwrite work you already have.

Two auth paths behind the same /mcp endpoint (spec §5.1 of docs/spec/ in the source):

  • Static Bearer token for personal / single-agent use. You mint one long random string, store its sha256 in the environment, and give the token to the agent.

  • OAuth 2.0 with PKCE + Dynamic Client Registration for connectors that expect it (ChatGPT's connector is the current case that requires it).

Both paths can coexist. Both are optional — leave both unset and the /mcp route never mounts.

Quick start

Two paths depending on how you want to run it: from source (for development, or if you prefer to manage Python yourself), or via Docker Compose (for a stable single-host install).

From source

Requires Python 3.12 and uv, plus Node 20+ for the frontend.

# 1. Install Python deps and set up the venv
uv sync

# 2. Initialize / migrate the database (creates ./data/studio.sqlite3)
uv run studio init-db

# 3. Start the API server on http://127.0.0.1:8000 (local mode — no auth)
uv run studio serve

# 4. In another terminal, run the frontend dev server
cd frontend
npm install
npm run dev        # http://localhost:5173, proxies to the API

Local mode binds to loopback and skips authentication for developer convenience. To try the auth flow locally, follow the "Enabling remote mode" section below.

Other CLI commands:

uv run studio db-backup            # verified online SQLite backup
uv run studio db-restore <path> --confirm-database ./data/studio.sqlite3
uv run studio export               # full JSON + uploads takeout
uv run studio schedule-tick        # run the periodic maintenance jobs once
uv run studio hash-password        # interactively hash a password for STUDIO_AUTH_PASSWORD_HASH

Run the tests:

uv run pytest                      # backend
cd frontend && npm test            # frontend

Docker Compose

The docker-compose.yml in this repo defines three services: app (the FastAPI + built SPA), scheduler (a 60-second-tick loop that runs backups, reminders, retention), and cloudflared (a reference reverse-proxy sidecar — swap it for whatever fits your infrastructure).

Reverse-proxying / TLS is deliberately out of scope of the app: pick your own. Reasonable choices include:

  • Cloudflare Tunnel (the reference cloudflared service in docker-compose.yml, with the provisioning script in scripts/provision-cloudflare-tunnel.py).

  • Caddy or Nginx as a host-level reverse proxy, terminating TLS with your own certs.

  • Tailscale Funnel for private-first hosting.

  • Just SSH forward -L 8000 if you only want it on your own machine.

If you use Cloudflare Tunnel, either edit or remove the cloudflared service and unset STUDIO_TRUSTED_PROXY_IPS in .env.production. If you use another proxy, set STUDIO_TRUSTED_PROXY_IPS to your proxy's IP so real client IPs land in the audit log.

Deployment steps (once your Docker host is ready):

# 1. Build the frontend locally — the server never builds it.
cd frontend && npm ci && npm run build && cd ..

# 2. Copy the env template and fill in the required secrets.
cp deploy/env.production.example .env.production
chmod 600 .env.production
$EDITOR .env.production

# 3. Generate a scrypt-hashed password for STUDIO_AUTH_PASSWORD_HASH.
uv run studio hash-password
# Paste the "password_hash" value into .env.production, single-quoted.

# 4. Build and start.
docker compose --env-file .env.production build
docker compose --env-file .env.production up -d

docs/deploy.md has a longer walkthrough covering the reference layout, the backup automation scripts under scripts/, and the operation lock the deploy and backup jobs share.

Enabling remote mode

Remote mode is what turns the app from "local dev with no auth" to "public URL behind a proxy with session cookies". Set at minimum:

  • STUDIO_MODE=remote

  • STUDIO_SESSION_SECRET — a random string, at least 32 characters.

  • STUDIO_AUTH_PASSWORD_HASH — the output of uv run studio hash-password.

  • STUDIO_ALLOWED_HOSTS — the exact hostname(s) the app will answer on (no wildcards; the app will refuse to boot with *).

  • STUDIO_TRUSTED_PROXY_IPS — if there is a reverse proxy in front, the IP(s) it uses to talk to the app.

The app refuses to boot in remote mode if any of the required secrets are missing or if allowed_hosts is empty — this is deliberate. There is no "silently open" configuration.

Configuration

Most values live in environment variables (production is Docker-friendly that way). A subset can also live in a TOML file loaded by --config or STUDIO_CONFIG — see config/config.example.toml for the shape.

Secrets are env-only by design: they are never read from the TOML config, so bundling the config file with a deployment can never leak them.

Variable

Purpose

Default

STUDIO_MODE

local (loopback-only, no auth) or remote (session cookies + password)

local

STUDIO_BIND_HOST

Address the server binds to

127.0.0.1

STUDIO_BIND_PORT

Port the server binds to

8000

STUDIO_ALLOWED_HOSTS

Comma-separated list of hostnames accepted in Host: header (required in remote mode)

(empty)

STUDIO_TRUSTED_PROXY_IPS

Comma-separated list of proxy IPs whose CF-Connecting-IP / X-Forwarded-For to trust

(empty)

STUDIO_SESSION_SECRET

Random string ≥32 chars used to sign session cookies (required in remote mode)

(empty)

STUDIO_AUTH_PASSWORD_HASH

scrypt-hashed login password from studio hash-password (required in remote mode)

(empty)

STUDIO_DATA_DIR

Where the SQLite database lives

./data

STUDIO_UPLOADS_DIR

Where uploaded attachments live

<data-dir>/uploads

STUDIO_BACKUPS_DIR

Where SQLite online backups are written

./backups

STUDIO_LOGS_DIR

Where app logs go

./logs

STUDIO_BACKUP_STATE_DIR

Optional read-only path where the host's backup jobs drop capacity.json for the in-app status widget

(unset — status shows unknown)

STUDIO_UPLOAD_MAX_FILE_BYTES

Per-file upload cap

26214400 (25 MiB)

STUDIO_UPLOAD_QUOTA_BYTES

Total upload quota per mother-project subtree

2147483648 (2 GiB)

STUDIO_UPLOAD_MAX_IMAGE_PIXELS

Decompression-bomb guard

40000000 (40M px)

STUDIO_RADAR_TOKEN_HASH

sha256 hex of the intake channel's bearer token; unset disables the intake endpoint

(unset)

STUDIO_MCP_TOKEN_HASH

sha256 hex of the MCP static-bearer token; unset disables the static-Bearer path

(unset)

STUDIO_MCP_OAUTH_ISSUER_URL

Public URL that hosts the OAuth AS metadata; setting it wakes the OAuth path

(unset)

STUDIO_MCP_OAUTH_ALLOWED_REDIRECT_HOSTS

Comma-separated hostnames allowed in DCR redirect URIs (loopback always allowed)

chatgpt.com

STUDIO_MCP_OAUTH_ACCESS_TOKEN_TTL_SECONDS

OAuth access-token lifetime

3600

STUDIO_MCP_OAUTH_REFRESH_TOKEN_TTL_SECONDS

OAuth refresh-token lifetime

2592000 (30 days)

STUDIO_MCP_OAUTH_CODE_TTL_SECONDS

OAuth authorization-code lifetime

300

Generating hashed tokens

The intake endpoint and the MCP static-Bearer path both store sha256(token) — never the token itself — so a leaked .env.production yields nothing replayable.

# Generate a token and its hash. The token goes to whichever caller needs it
# (your external intake, your MCP client). The hash goes into .env.production.
TOKEN=$(python -c "import secrets; print(secrets.token_urlsafe(32))")
printf %s "$TOKEN" | sha256sum | cut -d' ' -f1   # → STUDIO_RADAR_TOKEN_HASH / STUDIO_MCP_TOKEN_HASH
echo "$TOKEN"                                     # → give to the caller, nowhere else

External intake: hand your own feed to the inbox

There is an HTTP endpoint designed to receive items from an external feeder — an RSS scraper, a topic-radar tool, a scheduled scrape job, whatever ingests content on your behalf. The endpoint is generic: bring your own upstream, wire it here, and items land in the inbox where you triage them.

Endpoint: POST /api/inbox

Authentication: Authorization: Bearer <token>. The server compares sha256(token) against STUDIO_RADAR_TOKEN_HASH in constant time. If that env var is unset, the endpoint returns 401 to every Bearer call — the intake stays fully closed.

CSRF is not required on this path: the CSRF cookie defends against browser session replay, which is not a threat when the caller supplies its own bearer header.

Request body (JSON):

Field

Type

Notes

title

string, required, ≤500 chars

The inbox item title. Blank / missing → 400.

first_reaction

string, optional

Your one-line hot take.

links

string, optional

Free text — pasted URLs are fine.

radar_topic_id

string, optional, ≤500 chars

Your feeder's identifier for this topic. Second-strongest dedup key.

canonical_url

string, optional, ≤2000 chars

Canonical URL of the item. Third-strongest dedup key.

idempotency_key

string, optional, ≤500 chars

Per-delivery unique key. Strongest dedup key.

Dedup priority: idempotency_key > radar_topic_id > canonical_url. On a repeat delivery, the server returns the row that already exists rather than creating a second one — even if you had already discarded or converted that row. Re-delivery must not overturn your triage decision.

Response:

  • 201 Created — a brand-new row was inserted.

  • 200 OK — a repeat delivery was matched to an existing row (any status, including discarded / converted). Same body shape.

  • 400 Bad Request — missing / invalid title.

  • 401 Unauthorized — bad or missing bearer token, or intake not configured.

Response body:

{
  "item": {
    "id": 42,
    "title": "…",
    "source": "radar",
    "status": "pending",
    "created_at": "2026-08-12T12:34:56Z",
    "…": "…"
  },
  "deduplicated": false
}

Curl example:

curl -X POST https://studio.example.com/api/inbox \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Interesting minisite on typography systems",
    "first_reaction": "worth a look for the next essay",
    "links": "https://example.com/article",
    "canonical_url": "https://example.com/article",
    "idempotency_key": "myfeed-2026-08-12-a3f9"
  }'

The field is nicknamed "radar" throughout the codebase because it was originally wired to an external content-radar tool; the endpoint itself is generic and works with any feeder that can speak HTTP.

License

Yoru Studio is licensed under GNU Affero General Public License, version 3, only (AGPL-3.0-only). See LICENSE for the full text.

In one sentence: you may self-host, use, and modify the code freely for your own creative work; if you run a modified version as a network service that other people interact with, you must offer them the source of that modified version. This is exactly what AGPL is designed to enforce — the "network use" clause (§13) makes the reciprocal obligation trigger on running it, not just on distributing it.

Expectations

This is a personal project. It exists because one creator needed it and decided to share it.

  • Not a product. There is no roadmap that anyone else is entitled to, no support SLA, and no promise that the next release will not break your setup.

  • Maintained on the author's own rhythm. Issues and pull requests are welcome, but replies come when they come.

  • You self-host it. No hosted version exists. There is no plan for one.

  • Data lives on your machine. Nothing calls home. Nothing is sent to a third party. That is the point of self-hosting; it is also the reason nobody is going to rescue your data if you lose it. Take backups.

If any of that reads as "not for me", that is the honest signal — please pick something else and no hard feelings.

Contributing

Bug reports are welcome. Please include enough detail that the bug can be reproduced against a clean checkout.

Feature requests: this project scopes itself intentionally small and adds features only after real use exposes a need. A feature request that reads like "here is what I actually hit trying to use the app" is far more likely to land than one that reads like "here is a nice thing to have".

Pull requests: for anything larger than a one-file bug fix, please open an issue first to check that the direction fits. AGPL-3.0-only means contributions must be compatible with that license — by opening a pull request you agree that your contribution is under the same terms as the rest of the project.

Attribution

Built by Yoru, Claude Fable 5, and GPT 5.6 Sol — the three of us.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    -
    quality
    A
    maintenance
    Server-enforced workflow discipline for AI agents. An MCP server providing persistent work items, dependency graphs, quality gates, and actor attribution. Schemas define what agents must produce — the server blocks the call if they don't. Works with any MCP-compatible client.
    199
    MIT
  • A
    license
    -
    quality
    B
    maintenance
    MCP server for task management, project knowledge, workspace trust, runner sandboxes, extension registry, and workflow prompts, enabling AI agents to manage tasks and collaborate locally.
    5,117
    Apache 2.0
  • A
    license
    -
    quality
    C
    maintenance
    A self-hosted MCP server that gives AI agents controlled access to a machine: filesystem, shell, background processes, git, web fetching and persistent key-value memory.
    GPL 3.0

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/yoruuuchan/yoru-studio-oss'

If you have feedback or need assistance with the MCP directory API, please join our Discord server